text: #!/usr/bin/python
  # Copyright (c) 2012 The Native Client Authors. All rights reserved.
  # Use of this source code is governed by a BSD-style license that can be
  # found in the LICENSE file.
  
  import argparse
  import os
  import subprocess
  import sys
  import tempfile
  
  import pynacl.platform
  
  # Target architecture for PNaCl can be set through the ``-arch``
  # command-line argument, and when its value is ``env`` the following
  # program environment variable is queried to figure out which
  # architecture to target.
  ARCH_ENV_VAR_NAME = 'PNACL_RUN_ARCH'
  
  class Environment:
    pass
  env = Environment()
  
  def SetupEnvironment():
    # native_client/ directory
    env.nacl_root = FindBaseDir()
  
    env.toolchain_base = os.path.join(env.nacl_root,
                                      'toolchain',
                                      '%s_x86' % pynacl.platform.GetOS())
  
    # Path to PNaCl toolchain
    env.pnacl_base = os.path.join(env.toolchain_base, 'pnacl_newlib')
  
    # QEMU
    env.arm_root = os.path.join(env.toolchain_base, 'arm_trusted')
    env.qemu_arm = os.path.join(env.arm_root, 'run_under_qemu_arm')
  
    env.mips32_root = os.path.join(env.toolchain_base, 'mips_trusted')
    env.qemu_mips32 = os.path.join(env.mips32_root, 'run_under_qemu_mips32')
  
    # Path to 'readelf'
    env.readelf = FindReadElf()
  
    # Path to 'scons'
    env.scons = os.path.join(env.nacl_root, 'scons')
  
    # Library path for dynamic linker.
    env.library_path = []
  
    # Suppress -S -a
    env.paranoid = False
  
    # Only print commands, don't run them
    env.dry_run = False
  
    # Force a specific sel_ldr
    env.force_sel_ldr = None
  
    # Force a specific IRT
    env.force_irt = None
  
    # Don't print anything
    env.quiet = False
  
    # Arch (x86-32, x86-64, arm, mips32)
    env.arch = None
  
    # Trace in QEMU
    env.trace = False
  
    # Debug the nexe using the debug stub
    env.debug = False
  
    # PNaCl (as opposed to NaCl).
    env.is_pnacl = False
  
  def PrintBanner(output):
    if not env.quiet:
      lines = output.split('\n')
      print '*' * 80
      for line in lines:
        padding = ' ' * max(0, (80 - len(line)) / 2)
        print padding + output + padding
      print '*' * 80
  
  def PrintCommand(s):
    if not env.quiet:
      print
      print s
      print
  
  def SetupArch(arch, allow_build=True):
    '''Setup environment variables that require knowing the
       architecture. We can only do this after we've seen the
       nexe or once we've read -arch off the command-line.
    '''
    env.arch = arch
    # Path to Native NaCl toolchain (glibc)
    # MIPS does not have glibc support.
    if arch != 'mips32':
      toolchain_arch, tooldir_arch, libdir = {
          'x86-32': ('x86', 'x86_64', 'lib32'),
          'x86-64': ('x86', 'x86_64', 'lib'),
          'arm': ('arm', 'arm', 'lib'),
          }[arch]
      env.nnacl_tooldir = os.path.join(env.toolchain_base,
                                       'nacl_%s_glibc' % toolchain_arch,
                                       '%s-nacl' % tooldir_arch)
      env.nnacl_libdir = os.path.join(env.nnacl_tooldir, libdir)
    env.sel_ldr = FindOrBuildSelLdr(allow_build=allow_build)
    env.irt = FindOrBuildIRT(allow_build=allow_build)
    if arch == 'arm':
      env.elf_loader = FindOrBuildElfLoader(allow_build=allow_build)
  
  
  def SetupLibC(arch, is_dynamic):
    if is_dynamic:
      if env.is_pnacl:
        libdir = os.path.join(env.pnacl_base, 'lib-' + arch)
      else:
        libdir = env.nnacl_libdir
      env.runnable_ld = os.path.join(libdir, 'runnable-ld.so')
      env.library_path.append(libdir)
  
  
  def main(argv):
    SetupEnvironment()
    return_code = 0
  
    sel_ldr_options = []
    # sel_ldr's "quiet" options need to come early in the command line
    # to suppress noisy output from processing other options, like -Q.
    sel_ldr_quiet_options = []
    nexe, nexe_params = ArgSplit(argv[1:])
  
    try:
      if env.is_pnacl:
        nexe = Translate(env.arch, nexe)
  
      # Read the ELF file info
      if env.is_pnacl and env.dry_run:
        # In a dry run, we don't actually run pnacl-translate, so there is
        # no nexe for readelf. Fill in the information manually.
        arch = env.arch
        is_dynamic = False
        is_glibc_static = False
      else:
        arch, is_dynamic, is_glibc_static = ReadELFInfo(nexe)
  
      # Add default sel_ldr options
      if not env.paranoid:
        # Enable mmap() for loading the nexe, so that 'perf' gives usable output.
        # Note that this makes the sandbox unsafe if the mmap()'d nexe gets
        # modified while sel_ldr is running.
        os.environ['NACL_FAULT_INJECTION'] = \
          'ELF_LOAD_BYPASS_DESCRIPTOR_SAFETY_CHECK=GF1/999'
        sel_ldr_options += ['-a']
        # -S signal handling is not supported on windows, but otherwise
        # it is useful getting the address of crashes.
        if not pynacl.platform.IsWindows():
          sel_ldr_options += ['-S']
        # X86-64 glibc static has validation problems without stub out (-s)
        if arch == 'x86-64' and is_glibc_static:
          sel_ldr_options += ['-s']
      if env.quiet:
        # Don't print sel_ldr logs
        # These need to be at the start of the arglist for full effectiveness.
        # -q means quiet most stderr warnings.
        # -l /dev/null means log to /dev/null.
        sel_ldr_quiet_options = ['-q', '-l', '/dev/null']
      if env.debug:
        # Disabling validation (-c) is used by the debug stub test.
        # TODO(dschuff): remove if/when it's no longer necessary
        sel_ldr_options += ['-c', '-c', '-g']
  
      # Tell the user
      if is_dynamic:
        extra = 'DYNAMIC'
      else:
        extra = 'STATIC'
      PrintBanner('%s is %s %s' % (os.path.basename(nexe),
                                   arch.upper(), extra))
  
      # Setup architecture-specific environment variables
      SetupArch(arch)
  
      # Setup LibC-specific environment variables
      SetupLibC(arch, is_dynamic)
  
      # Add IRT to sel_ldr options.
      if env.irt:
        sel_ldr_options += ['-B', env.irt]
  
      # The NaCl dynamic loader prefers posixy paths.
      nexe_path = os.path.abspath(nexe)
      nexe_path = nexe_path.replace('\\', '/')
      sel_ldr_nexe_args = [nexe_path] + nexe_params
  
      if is_dynamic:
        ld_library_path = ':'.join(env.library_path)
        if arch == 'arm':
          sel_ldr_nexe_args = [env.elf_loader, '--interp-prefix',
                               env.nnacl_tooldir] + sel_ldr_nexe_args
          sel_ldr_options += ['-E', 'LD_LIBRARY_PATH=' + ld_library_path]
        else:
          sel_ldr_nexe_args = [env.runnable_ld, '--library-path',
                               ld_library_path] + sel_ldr_nexe_args
  
      # Setup sel_ldr arguments.
      sel_ldr_args = sel_ldr_options + ['--'] + sel_ldr_nexe_args
  
      # Run sel_ldr!
      retries = 0
      try:
        if hasattr(env, 'retries'):
          retries = int(env.retries)
      except ValueError:
        pass
      collate = env.collate or retries > 0
      input = sys.stdin.read() if collate else None
      for iter in range(1 + max(retries, 0)):
        output = RunSelLdr(sel_ldr_args, quiet_args=sel_ldr_quiet_options,
                           collate=collate, stdin_string=input)
        if env.last_return_code < 128:
          # If the application crashes, we expect a 128+ return code.
          break
      sys.stdout.write(output or '')
      return_code = env.last_return_code
    finally:
      if env.is_pnacl:
        # Clean up the .nexe that was created.
        try:
          os.remove(nexe)
        except:
          pass
  
    return return_code
  
  
  def RunSelLdr(args, quiet_args=[], collate=False, stdin_string=None):
    """Run the sel_ldr command and optionally capture its output.
  
    Args:
      args: A string list containing the command and arguments.
      collate: Whether to capture stdout+stderr (rather than passing
          them through to the terminal).
      stdin_string: Text to send to the command via stdin.  If None, stdin is
          inherited from the caller.
  
    Returns:
      A string containing the concatenation of any captured stdout plus
      any captured stderr.
    """
    prefix = []
    # The bootstrap loader args (--r_debug, --reserved_at_zero) need to
    # come before quiet_args.
    bootstrap_loader_args = []
    arch = pynacl.platform.GetArch3264()
    if arch != pynacl.platform.ARCH3264_ARM and env.arch == 'arm':
      prefix = [ env.qemu_arm, '-cpu', 'cortex-a9']
      if env.trace:
        prefix += ['-d', 'in_asm,op,exec,cpu']
      args = ['-Q'] + args
  
    if arch != pynacl.platform.ARCH3264_MIPS32 and env.arch == 'mips32':
      prefix = [env.qemu_mips32]
      if env.trace:
        prefix += ['-d', 'in_asm,op,exec,cpu']
      args = ['-Q'] + args
  
    # Use the bootstrap loader on linux.
    if pynacl.platform.IsLinux():
      bootstrap = os.path.join(os.path.dirname(env.sel_ldr),
                               'nacl_helper_bootstrap')
      loader = [bootstrap, env.sel_ldr]
      template_digits = 'X' * 16
      bootstrap_loader_args = ['--r_debug=0x' + template_digits,
                               '--reserved_at_zero=0x' + template_digits]
    else:
      loader = [env.sel_ldr]
    return Run(prefix + loader + bootstrap_loader_args + quiet_args + args,
               exit_on_failure=(not collate),
               capture_stdout=collate, capture_stderr=collate,
               stdin_string=stdin_string)
  
  
  def FindOrBuildIRT(allow_build = True):
    if env.force_irt:
      if env.force_irt == 'none':
        return None
      elif env.force_irt == 'core':
        flavors = ['irt_core']
      else:
        irt = env.force_irt
        if not os.path.exists(irt):
          Fatal('IRT not found: %s' % irt)
        return irt
    else:
      flavors = ['irt_core']
  
    irt_paths = []
    for flavor in flavors:
      path = os.path.join(env.nacl_root, 'scons-out',
                          'nacl_irt-%s/staging/%s.nexe' % (env.arch, flavor))
      irt_paths.append(path)
  
    for path in irt_paths:
      if os.path.exists(path):
        return path
  
    if allow_build:
      PrintBanner('irt not found. Building it with scons.')
      irt = irt_paths[0]
      BuildIRT(flavors[0])
      assert(env.dry_run or os.path.exists(irt))
      return irt
  
    return None
  
  def BuildIRT(flavor):
    args = ('platform=%s naclsdk_validate=0 ' +
            'sysinfo=0 -j8 %s') % (env.arch, flavor)
    args = args.split()
    Run([env.scons] + args, cwd=env.nacl_root)
  
  def FindOrBuildElfLoader(allow_build=True):
    if env.force_elf_loader:
      if env.force_elf_loader == 'none':
        return None
      if not os.path.exists(env.force_elf_loader):
        Fatal('elf_loader.nexe not found: %s' % env.force_elf_loader)
      return env.force_elf_loader
  
    path = os.path.join(env.nacl_root, 'scons-out',
                        'nacl-' + env.arch,
                        'staging', 'elf_loader.nexe')
    if os.path.exists(path):
      return path
  
    if allow_build:
      PrintBanner('elf_loader not found.  Building it with scons.')
      Run([env.scons, 'platform=' + env.arch,
           'naclsdk_validate=0', 'sysinfo=0',
           '-j8', 'elf_loader'],
          cwd=env.nacl_root)
      assert(env.dry_run or os.path.exists(path))
      return path
  
    return None
  
  def FindOrBuildSelLdr(allow_build=True):
    if env.force_sel_ldr:
      if env.force_sel_ldr in ('dbg','opt'):
        modes = [ env.force_sel_ldr ]
      else:
        sel_ldr = env.force_sel_ldr
        if not os.path.exists(sel_ldr):
          Fatal('sel_ldr not found: %s' % sel_ldr)
        return sel_ldr
    else:
      modes = ['opt','dbg']
  
    loaders = []
    for mode in modes:
      sel_ldr = os.path.join(
          env.nacl_root, 'scons-out',
          '%s-%s-%s' % (mode, pynacl.platform.GetOS(), env.arch),
          'staging', 'sel_ldr')
      if pynacl.platform.IsWindows():
        sel_ldr += '.exe'
      loaders.append(sel_ldr)
  
    # If one exists, use it.
    for sel_ldr in loaders:
      if os.path.exists(sel_ldr):
        return sel_ldr
  
    # Build it
    if allow_build:
      PrintBanner('sel_ldr not found. Building it with scons.')
      sel_ldr = loaders[0]
      BuildSelLdr(modes[0])
      assert(env.dry_run or os.path.exists(sel_ldr))
      return sel_ldr
  
    return None
  
  def BuildSelLdr(mode):
    args = ('platform=%s MODE=%s-host naclsdk_validate=0 ' +
            'sysinfo=0 -j8 sel_ldr') % (env.arch, mode)
    args = args.split()
    Run([env.scons] + args, cwd=env.nacl_root)
  
  def Translate(arch, pexe):
    output_file = os.path.splitext(pexe)[0] + '.' + arch + '.nexe'
    pnacl_translate = os.path.join(env.pnacl_base, 'bin', 'pnacl-translate')
    args = [ pnacl_translate, '-arch', arch, pexe, '-o', output_file,
             '--allow-llvm-bitcode-input' ]
    Run(args)
    return output_file
  
  def Stringify(args):
    ret = ''
    for arg in args:
      if ' ' in arg:
        ret += ' "%s"' % arg
      else:
        ret += ' %s' % arg
    return ret.strip()
  
  def PrepareStdin(stdin_string):
    """Prepare a stdin stream for a subprocess based on contents of a string.
    This has to be in the form of an actual file, rather than directly piping
    the string, since the child may (inappropriately) try to fseek() on stdin.
  
    Args:
      stdin_string: The characters to pipe to the subprocess.
  
    Returns:
      An open temporary file object ready to be read from.
    """
    f = tempfile.TemporaryFile()
    f.write(stdin_string)
    f.seek(0)
    return f
  
  def Run(args, cwd=None, verbose=True, exit_on_failure=False,
          capture_stdout=False, capture_stderr=False, stdin_string=None):
    """Run a command and optionally capture its output.
  
    Args:
      args: A string list containing the command and arguments.
      cwd: Change to this directory before running.
      verbose: Print the command before running it.
      exit_on_failure: Exit immediately if the command returns nonzero.
      capture_stdout: Capture the stdout as a string (rather than passing it
          through to the terminal).
      capture_stderr: Capture the stderr as a string (rather than passing it
          through to the terminal).
      stdin_string: Text to send to the command via stdin.  If None, stdin is
          inherited from the caller.
  
    Returns:
      A string containing the concatenation of any captured stdout plus
      any captured stderr.
    """
    if verbose:
      PrintCommand(Stringify(args))
      if env.dry_run:
        return
  
    stdout_redir = None
    stderr_redir = None
    stdin_redir = None
    if capture_stdout:
      stdout_redir = subprocess.PIPE
    if capture_stderr:
      stderr_redir = subprocess.PIPE
    if stdin_string:
      stdin_redir = PrepareStdin(stdin_string)
  
    p = None
    try:
      # PNaCl toolchain executables (pnacl-translate, readelf) are scripts
      # not binaries, so it doesn't want to run on Windows without a shell.
      use_shell = True if pynacl.platform.IsWindows() else False
      p = subprocess.Popen(args, stdin=stdin_redir, stdout=stdout_redir,
                           stderr=stderr_redir, cwd=cwd, shell=use_shell)
      (stdout_contents, stderr_contents) = p.communicate()
    except KeyboardInterrupt, e:
      if p:
        p.kill()
      raise e
    except BaseException, e:
      if p:
        p.kill()
      raise e
  
    env.last_return_code = p.returncode
  
    if p.returncode != 0 and exit_on_failure:
      if capture_stdout or capture_stderr:
        # Print an extra message if any of the program's output wasn't
        # going to the screen.
        Fatal('Failed to run: %s' % Stringify(args))
      sys.exit(p.returncode)
  
    return (stdout_contents or '') + (stderr_contents or '')
  
  
  def ArgSplit(argv):
    """Parse command-line arguments.
  
    Returns:
      Tuple (nexe, nexe_args) where nexe is the name of the nexe or pexe
      to execute, and nexe_args are its runtime arguments.
    """
    desc = ('Run a command-line nexe (or pexe). Automatically handles\n' +
            'translation, building sel_ldr, and building the IRT.')
    parser = argparse.ArgumentParser(description=desc)
    parser.add_argument('-L', action='append', dest='library_path', default=[],
                        help='Additional library path for dynamic linker.')
    parser.add_argument('--paranoid', action='store_true', default=False,
                        help='Remove -S (signals) and -a (file access) ' +
                        'from the default sel_ldr options, and disallow mmap() ' +
                        'for loading the nexe.')
    parser.add_argument('--loader', dest='force_sel_ldr', metavar='SEL_LDR',
                        help='Path to sel_ldr.  "dbg" or "opt" means use ' +
                        'dbg or opt version of sel_ldr. ' +
                        'By default, use whichever sel_ldr already exists; ' +
                        'otherwise, build opt version.')
    parser.add_argument('--irt', dest='force_irt', metavar='IRT',
                        help='Path to IRT nexe.  "core" or "none" means use ' +
                        'Core IRT or no IRT.  By default, use whichever IRT ' +
                        'already exists; otherwise, build irt_core.')
    parser.add_argument('--elf_loader', dest='force_elf_loader',
                        metavar='ELF_LOADER',
                        help='Path to elf_loader nexe.  ' +
                        'By default find it in scons-out, or build it.')
    parser.add_argument('--dry-run', '-n', action='store_true', default=False,
                        help="Just print commands, don't execute them.")
    parser.add_argument('--quiet', '-q', action='store_true', default=False,
                        help="Don't print anything.")
    parser.add_argument('--retries', default='0', metavar='N',
                        help='Retry sel_ldr command up to N times (if ' +
                        'flakiness is expected).  This argument implies ' +
                        '--collate.')
    parser.add_argument('--collate', action='store_true', default=False,
                        help="Combine/collate sel_ldr's stdout and stderr, and " +
                        "print to stdout.")
    parser.add_argument('--trace', '-t', action='store_true', default=False,
                        help='Trace qemu execution.')
    parser.add_argument('--debug', '-g', action='store_true', default=False,
                        help='Run sel_ldr with debugging enabled.')
    parser.add_argument('-arch', '-m', dest='arch', action='store',
                        choices=sorted(
                          pynacl.platform.ARCH3264_LIST + ['env']),
                        help=('Specify architecture for PNaCl translation. ' +
                              '"env" is a special value which obtains the ' +
                              'architecture from the environment ' +
                              'variable "%s".') % ARCH_ENV_VAR_NAME)
    parser.add_argument('remainder', nargs=argparse.REMAINDER,
                        metavar='nexe/pexe + args')
    (options, args) = parser.parse_known_args(argv)
  
    # Copy the options into env.
    for (key, value) in vars(options).iteritems():
      setattr(env, key, value)
  
    args += options.remainder
    nexe = args[0] if len(args) else ''
    env.is_pnacl = nexe.endswith('.pexe')
  
    if env.arch == 'env':
      # Get the architecture from the environment.
      try:
        env.arch = os.environ[ARCH_ENV_VAR_NAME]
      except Exception as e:
        Fatal(('Option "-arch env" specified, but environment variable ' +
              '"%s" not specified: %s') % (ARCH_ENV_VAR_NAME, e))
    if not env.arch and env.is_pnacl:
      # For NaCl we'll figure out the architecture from the nexe's
      # architecture, but for PNaCl we first need to translate and the
      # user didn't tell us which architecture to translate to. Be nice
      # and just translate to the current machine's architecture.
      env.arch = pynacl.platform.GetArch3264()
    # Canonicalize env.arch.
    env.arch = pynacl.platform.GetArch3264(env.arch)
    return nexe, args[1:]
  
  
  def Fatal(msg, *args):
    if len(args) > 0:
      msg = msg % args
    print msg
    sys.exit(1)
  
  
  def FindReadElf():
    '''Returns the path of "readelf" binary.'''
  
    candidates = []
    # Use PNaCl's if it available.
    candidates.append(
      os.path.join(env.pnacl_base, 'bin', 'pnacl-readelf'))
  
    # Otherwise, look for the system readelf
    for path in os.environ['PATH'].split(os.pathsep):
      candidates.append(os.path.join(path, 'readelf'))
  
    for readelf in candidates:
      if os.path.exists(readelf):
        return readelf
  
    Fatal('Cannot find readelf!')
  
  
  def ReadELFInfo(f):
    ''' Returns: (arch, is_dynamic, is_glibc_static) '''
  
    readelf = env.readelf
    readelf_out = Run([readelf, '-lh', f], capture_stdout=True, verbose=False)
  
    machine_line = None
    is_dynamic = False
    is_glibc_static = False
    for line in readelf_out.split('\n'):
      line = line.strip()
      if line.startswith('Machine:'):
        machine_line = line
      if line.startswith('DYNAMIC'):
        is_dynamic = True
      if '__libc_atexit' in line:
        is_glibc_static = True
  
    if not machine_line:
      Fatal('Script error: readelf output did not make sense!')
  
    if 'Intel 80386' in machine_line:
      arch = 'x86-32'
    elif 'X86-64' in machine_line:
      arch = 'x86-64'
    elif 'ARM' in machine_line:
      arch = 'arm'
    elif 'MIPS' in machine_line:
      arch = 'mips32'
    else:
      Fatal('%s: Unknown machine type', f)
  
    return (arch, is_dynamic, is_glibc_static)
  
  
  def FindBaseDir():
    '''Crawl backwards, starting from the directory containing this script,
       until we find the native_client/ directory.
    '''
  
    curdir = os.path.abspath(sys.argv[0])
    while os.path.basename(curdir) != 'native_client':
      curdir,subdir = os.path.split(curdir)
      if subdir == '':
        # We've hit the file system root
        break
  
    if os.path.basename(curdir) != 'native_client':
      Fatal('Unable to find native_client directory!')
    return curdir
  
  
  if __name__ == '__main__':
    sys.exit(main(sys.argv))
  