def _Tester(self, exit_code=None, kill_signal=None):
        """Helper func for testing ExitAsStatus()

    Create a child to mimic the grandchild.
    Create a grandchild and have it exit/killed.
    Assert behavior based on exit/signal behavior.
    """
        pid = os.fork()
        if pid == 0:
            # Let the grandchild exit/kill itself.
            # The child should mimic the grandchild.
            status = _SpawnChild(exit_code=exit_code, kill_signal=kill_signal)
            try:
                process_util.ExitAsStatus(status)
            except SystemExit as e:
                # pylint: disable=W0212
                os._exit(e.code)
            raise AssertionError('ERROR: should have exited!')

        # The parent returns the child's status.
        status = os.waitpid(pid, 0)[1]
        if exit_code is not None:
            self.assertFalse(os.WIFSIGNALED(status))
            self.assertTrue(os.WIFEXITED(status))
            self.assertEqual(os.WEXITSTATUS(status), exit_code)
        else:
            self.assertFalse(os.WIFEXITED(status))
            self.assertTrue(os.WIFSIGNALED(status))
            self.assertEqual(os.WTERMSIG(status), kill_signal)
    def run(self):
        """Runs the test in a proper environment (e.g. qemu)."""

        # We know these pre-tests are fast (especially if they've already been run
        # once), so run them automatically for the user if they test by hand.
        self.pre_test()

        paths_to_mount = (list(self._BIND_MOUNT_PATHS) + [
            mount for mount in self._BIND_MOUNT_IF_NOT_SYMLINK_PATHS
            if not os.path.islink('/' + mount)
        ])
        for mount in paths_to_mount:
            path = os.path.join(self.sysroot, mount)
            osutils.SafeMakedirs(path)
            osutils.Mount('/' + mount, path, 'none', osutils.MS_BIND)

        positive_filters = self.gtest_filter[0]
        negative_filters = self.gtest_filter[1]

        if self.user_gtest_filter:
            positive_filters += self.user_gtest_filter[0]
            negative_filters += self.user_gtest_filter[1]

        filters = (':'.join(positive_filters), ':'.join(negative_filters))
        gtest_filter = '%s-%s' % filters

        cmd = self.removeSysrootPrefix(self.bin)
        argv = self.args[:]
        argv[0] = self.removeSysrootPrefix(argv[0])
        if gtest_filter != '-':
            argv.append('--gtest_filter=' + gtest_filter)

        # Some programs expect to find data files via $CWD, so doing a chroot
        # and dropping them into / would make them fail.
        cwd = self.removeSysrootPrefix(os.getcwd())

        # Make orphaned child processes reparent to this process instead of the init
        # process.  This allows us to kill them if they do not terminate after the
        # test has finished running.
        _MakeProcessSubreaper()

        # Fork off a child to run the test.  This way we can make tweaks to the
        # env that only affect the child (gid/uid/chroot/cwd/etc...).  We have
        # to fork anyways to run the test, so might as well do it all ourselves
        # to avoid (slow) chaining through programs like:
        #   sudo -u $SUDO_UID -g $SUDO_GID chroot $SYSROOT bash -c 'cd $CWD; $BIN'
        child = os.fork()
        if child == 0:
            print('chroot: %s' % self.sysroot)
            print('cwd: %s' % cwd)
            print('cmd: {%s} %s' % (cmd, ' '.join(map(repr, argv))))
            os.chroot(self.sysroot)
            os.chdir(cwd)

            # Set the child's pgid to its pid, so we can kill any processes that the
            # child creates after the child terminates.
            os.setpgid(0, 0)

            # Remove sysroot from path environment variables.
            for var in ('OUT', 'SRC', 'T'):
                if var in os.environ:
                    os.environ[var] = self.removeSysrootPrefix(os.environ[var])

            # The TERM the user is leveraging might not exist in the sysroot.
            # Force a sane default that supports standard color sequences.
            os.environ['TERM'] = 'ansi'
            # Some progs want this like bash else they get super confused.
            os.environ['PWD'] = cwd
            os.environ['GTEST_COLOR'] = 'yes'
            if not self.run_as_root:
                user, uid, gid, home = self.GetNonRootAccount()
                os.setgid(gid)
                os.setuid(uid)
                os.environ['HOME'] = home
                os.environ['USER'] = user
            sys.exit(os.execvp(cmd, argv))

        proctitle.settitle('sysroot watcher', cmd)

        # Mask SIGINT with the assumption that the child will catch & process it.
        # We'll pass that back up below.
        signal.signal(signal.SIGINT, signal.SIG_IGN)

        # Reap any processes that were reparented to us until the child exits.
        status = _ReapUntilProcessExits(child)

        leaked_children = psutil.Process().get_children(recursive=True)
        if leaked_children:
            # It's possible the child forked and the forked processes are still
            # running.  Kill the forked processes.
            try:
                os.killpg(child, signal.SIGTERM)
            except OSError as e:
                if e.errno != errno.ESRCH:
                    print(
                        'Warning: while trying to kill pgid %s caught exception\n%s'
                        % (child, e),
                        file=sys.stderr)

            # Kill any orphaned processes originally created by the test that were in
            # a different process group.  This will also kill any processes that did
            # not respond to the SIGTERM.
            for child in leaked_children:
                try:
                    child.kill()
                except psutil.NoSuchProcess:
                    pass

        failmsg = None
        if os.WIFSIGNALED(status):
            sig = os.WTERMSIG(status)
            failmsg = 'signal %s(%i)' % (signals.StrSignal(sig), sig)
        else:
            exit_status = os.WEXITSTATUS(status)
            if exit_status:
                failmsg = 'exit code %i' % exit_status
        if failmsg:
            print('Error: %s: failed with %s' % (cmd, failmsg),
                  file=sys.stderr)

        if leaked_children:
            for p in leaked_children:
                print(
                    'Error: the test leaked process %s with pid %s (it was forcefully'
                    ' killed)' % (p.name(), p.pid),
                    file=sys.stderr)
            # TODO(vapier): Make this an error.  We need to track down some scenarios
            # where processes do leak though before we can make this fatal :(.
            #sys.exit(100)

        process_util.ExitAsStatus(status)
def CreatePidNs():
    """Start a new pid namespace

  This will launch all the right manager processes.  The child that returns
  will be isolated in a new pid namespace.

  If functionality is not available, then it will return w/out doing anything.

  A note about the processes generated as a result of calling this function:
  You call CreatePidNs() in pid X
  - X launches Pid Y,
    - Pid X will now do nothing but wait for Pid Y to finish and then sys.exit()
      with that return code
    - Y launches Pid Z
      - Pid Y will now do nothing but wait for Pid Z to finish and then
        sys.exit() with that return code
      - **Pid Z returns from CreatePidNs**. So, the caller of this function
        continues in a different process than the one that made the call.
          - All SIGTERM/SIGINT signals are forwarded down from pid X to pid Z to
            handle.
          - SIGKILL will only kill pid X, and leak Pid Y and Z.

  Returns:
    The last pid outside of the namespace. (i.e., pid X)
  """
    first_pid = os.getpid()

    try:
        # First create the namespace.
        Unshare(CLONE_NEWPID)
    except OSError as e:
        if e.errno == errno.EINVAL:
            # For older kernels, or the functionality is disabled in the config,
            # return silently.  We don't want to hard require this stuff.
            return first_pid
        else:
            # For all other errors, abort.  They shouldn't happen.
            raise

    # Used to make sure process groups are in the right state before we try to
    # forward the controlling terminal.
    lock = locking.PipeLock()

    # Now that we're in the new pid namespace, fork.  The parent is the master
    # of it in the original namespace, so it only monitors the child inside it.
    # It is only allowed to fork once too.
    pid = os.fork()
    if pid:
        proctitle.settitle('pid ns', 'external init')

        # We forward termination signals to the child and trust the child to respond
        # sanely. Later, ExitAsStatus propagates the exit status back up.
        _ForwardToChildPid(pid, signal.SIGINT)
        _ForwardToChildPid(pid, signal.SIGTERM)

        # Forward the control of the terminal to the child so it can manage input.
        _SafeTcSetPgrp(sys.stdin.fileno(), pid)

        # Signal our child it can move forward.
        lock.Post()
        del lock

        # Reap the children as the parent of the new namespace.
        process_util.ExitAsStatus(_ReapChildren(pid))
    else:
        # Make sure to unshare the existing mount point if needed.  Some distros
        # create shared mount points everywhere by default.
        try:
            osutils.Mount('none', '/proc', 0,
                          osutils.MS_PRIVATE | osutils.MS_REC)
        except OSError as e:
            if e.errno != errno.EINVAL:
                raise

        # The child needs its own proc mount as it'll be different.
        osutils.Mount(
            'proc', '/proc', 'proc', osutils.MS_NOSUID | osutils.MS_NODEV
            | osutils.MS_NOEXEC | osutils.MS_RELATIME)

        # Wait for our parent to finish initialization.
        lock.Wait()
        del lock

        # Resetup the locks for the next phase.
        lock = locking.PipeLock()

        pid = os.fork()
        if pid:
            proctitle.settitle('pid ns', 'init')

            # We forward termination signals to the child and trust the child to
            # respond sanely. Later, ExitAsStatus propagates the exit status back up.
            _ForwardToChildPid(pid, signal.SIGINT)
            _ForwardToChildPid(pid, signal.SIGTERM)

            # Now that we're in a new pid namespace, start a new process group so that
            # children have something valid to use.  Otherwise getpgrp/etc... will get
            # back 0 which tends to confuse -- you can't setpgrp(0) for example.
            os.setpgrp()

            # Forward the control of the terminal to the child so it can manage input.
            _SafeTcSetPgrp(sys.stdin.fileno(), pid)

            # Signal our child it can move forward.
            lock.Post()
            del lock

            # Watch all of the children.  We need to act as the master inside the
            # namespace and reap old processes.
            process_util.ExitAsStatus(_ReapChildren(pid))

    # Wait for our parent to finish initialization.
    lock.Wait()
    del lock

    # Create a process group for the grandchild so it can manage things
    # independent of the init process.
    os.setpgrp()

    # The grandchild will return and take over the rest of the sdk steps.
    return first_pid
Beispiel #4
0
def _ProxySimSetup(options):
  """Set up proxy simulator, and return only in the child environment.

  TODO: Ideally, this should support multiple concurrent invocations of
  cros_sdk --proxy-sim; currently, such invocations will conflict with each
  other due to the veth device names and IP addresses.  Either this code would
  need to generate fresh, unused names for all of these before forking, or it
  would need to support multiple concurrent cros_sdk invocations sharing one
  proxy and allowing it to exit when unused (without counting on any local
  service-management infrastructure on the host).
  """

  may_need_mpm = False
  apache_bin = osutils.Which('apache2')
  if apache_bin is None:
    apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
    if apache_bin is None:
      _ReportMissing(('apache2',))
  else:
    may_need_mpm = True

  # Module names and .so names included for ease of grepping.
  apache_modules = [('proxy_module', 'mod_proxy.so'),
                    ('proxy_connect_module', 'mod_proxy_connect.so'),
                    ('proxy_http_module', 'mod_proxy_http.so'),
                    ('proxy_ftp_module', 'mod_proxy_ftp.so')]

  # Find the apache module directory, and make sure it has the modules we need.
  module_dirs = {}
  for g in PROXY_APACHE_MODULE_GLOBS:
    for mod, so in apache_modules:
      for f in glob.glob(os.path.join(g, so)):
        module_dirs.setdefault(os.path.dirname(f), []).append(so)
  for apache_module_path, modules_found in module_dirs.iteritems():
    if len(modules_found) == len(apache_modules):
      break
  else:
    # Appease cros lint, which doesn't understand that this else block will not
    # fall through to the subsequent code which relies on apache_module_path.
    apache_module_path = None
    raise SystemExit(
        'Could not find apache module path containing all required modules: %s'
        % ', '.join(so for mod, so in apache_modules))

  def check_add_module(name):
    so = 'mod_%s.so' % name
    if os.access(os.path.join(apache_module_path, so), os.F_OK):
      mod = '%s_module' % name
      apache_modules.append((mod, so))
      return True
    return False

  check_add_module('authz_core')
  if may_need_mpm:
    for mpm in PROXY_APACHE_MPMS:
      if check_add_module('mpm_%s' % mpm):
        break

  veth_host = '%s-host' % PROXY_VETH_PREFIX
  veth_guest = '%s-guest' % PROXY_VETH_PREFIX

  # Set up locks to sync the net namespace setup.  We need the child to create
  # the net ns first, and then have the parent assign the guest end of the veth
  # interface to the child's new network namespace & bring up the proxy.  Only
  # then can the child move forward and rely on the network being up.
  ns_create_lock = locking.PipeLock()
  ns_setup_lock = locking.PipeLock()

  pid = os.fork()
  if not pid:
    # Create our new isolated net namespace.
    namespaces.Unshare(namespaces.CLONE_NEWNET)

    # Signal the parent the ns is ready to be configured.
    ns_create_lock.Post()
    del ns_create_lock

    # Wait for the parent to finish setting up the ns/proxy.
    ns_setup_lock.Wait()
    del ns_setup_lock

    # Set up child side of the network.
    commands = (
        ('ip', 'link', 'set', 'up', 'lo'),
        ('ip', 'address', 'add',
         '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
         'dev', veth_guest),
        ('ip', 'link', 'set', veth_guest, 'up'),
    )
    try:
      for cmd in commands:
        cros_build_lib.RunCommand(cmd, print_cmd=False)
    except cros_build_lib.RunCommandError:
      raise SystemExit('Running %r failed!' % (cmd,))

    proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
    for proto in ('http', 'https', 'ftp'):
      os.environ[proto + '_proxy'] = proxy_url
    for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
      os.environ.pop(v, None)
    return

  # Set up parent side of the network.
  uid = int(os.environ.get('SUDO_UID', '0'))
  gid = int(os.environ.get('SUDO_GID', '0'))
  if uid == 0 or gid == 0:
    for username in PROXY_APACHE_FALLBACK_USERS:
      try:
        pwnam = pwd.getpwnam(username)
        uid, gid = pwnam.pw_uid, pwnam.pw_gid
        break
      except KeyError:
        continue
    if uid == 0 or gid == 0:
      raise SystemExit('Could not find a non-root user to run Apache as')

  chroot_parent, chroot_base = os.path.split(options.chroot)
  pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
  log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)

  # Wait for the child to create the net ns.
  ns_create_lock.Wait()
  del ns_create_lock

  apache_directives = [
      'User #%u' % uid,
      'Group #%u' % gid,
      'PidFile %s' % pid_file,
      'ErrorLog %s' % log_file,
      'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
      'ServerName %s' % PROXY_HOST_IP,
      'ProxyRequests On',
      'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
  ] + [
      'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
      for (mod, so) in apache_modules
  ]
  commands = (
      ('ip', 'link', 'add', 'name', veth_host,
       'type', 'veth', 'peer', 'name', veth_guest),
      ('ip', 'address', 'add',
       '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK),
       'dev', veth_host),
      ('ip', 'link', 'set', veth_host, 'up'),
      ([apache_bin, '-f', '/dev/null'] +
       [arg for d in apache_directives for arg in ('-C', d)]),
      ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
  )
  cmd = None # Make cros lint happy.
  try:
    for cmd in commands:
      cros_build_lib.RunCommand(cmd, print_cmd=False)
  except cros_build_lib.RunCommandError:
    # Clean up existing interfaces, if any.
    cmd_cleanup = ('ip', 'link', 'del', veth_host)
    try:
      cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
    except cros_build_lib.RunCommandError:
      logging.error('running %r failed', cmd_cleanup)
    raise SystemExit('Running %r failed!' % (cmd,))

  # Signal the child that the net ns/proxy is fully configured now.
  ns_setup_lock.Post()
  del ns_setup_lock

  process_util.ExitAsStatus(os.waitpid(pid, 0)[1])
Beispiel #5
0
def CreatePidNs():
    """Start a new pid namespace

  This will launch all the right manager processes.  The child that returns
  will be isolated in a new pid namespace.

  If functionality is not available, then it will return w/out doing anything.

  Returns:
    The last pid outside of the namespace.
  """
    first_pid = os.getpid()

    try:
        # First create the namespace.
        Unshare(CLONE_NEWPID)
    except OSError as e:
        if e.errno == errno.EINVAL:
            # For older kernels, or the functionality is disabled in the config,
            # return silently.  We don't want to hard require this stuff.
            return first_pid
        else:
            # For all other errors, abort.  They shouldn't happen.
            raise

    # Used to make sure process groups are in the right state before we try to
    # forward the controlling terminal.
    lock = locking.PipeLock()

    # Now that we're in the new pid namespace, fork.  The parent is the master
    # of it in the original namespace, so it only monitors the child inside it.
    # It is only allowed to fork once too.
    pid = os.fork()
    if pid:
        proctitle.settitle('pid ns', 'external init')

        # Mask SIGINT with the assumption that the child will catch & process it.
        # We'll pass that back up below.
        signal.signal(signal.SIGINT, signal.SIG_IGN)

        # Forward the control of the terminal to the child so it can manage input.
        _SafeTcSetPgrp(sys.stdin.fileno(), pid)

        # Signal our child it can move forward.
        lock.Post()
        del lock

        # Reap the children as the parent of the new namespace.
        process_util.ExitAsStatus(_ReapChildren(pid))
    else:
        # Make sure to unshare the existing mount point if needed.  Some distros
        # create shared mount points everywhere by default.
        try:
            osutils.Mount('none', '/proc', 0,
                          osutils.MS_PRIVATE | osutils.MS_REC)
        except OSError as e:
            if e.errno != errno.EINVAL:
                raise

        # The child needs its own proc mount as it'll be different.
        osutils.Mount(
            'proc', '/proc', 'proc', osutils.MS_NOSUID | osutils.MS_NODEV
            | osutils.MS_NOEXEC | osutils.MS_RELATIME)

        # Wait for our parent to finish initialization.
        lock.Wait()
        del lock

        # Resetup the locks for the next phase.
        lock = locking.PipeLock()

        pid = os.fork()
        if pid:
            proctitle.settitle('pid ns', 'init')

            # Mask SIGINT with the assumption that the child will catch & process it.
            # We'll pass that back up below.
            signal.signal(signal.SIGINT, signal.SIG_IGN)

            # Now that we're in a new pid namespace, start a new process group so that
            # children have something valid to use.  Otherwise getpgrp/etc... will get
            # back 0 which tends to confuse -- you can't setpgrp(0) for example.
            os.setpgrp()

            # Forward the control of the terminal to the child so it can manage input.
            _SafeTcSetPgrp(sys.stdin.fileno(), pid)

            # Signal our child it can move forward.
            lock.Post()
            del lock

            # Watch all of the children.  We need to act as the master inside the
            # namespace and reap old processes.
            process_util.ExitAsStatus(_ReapChildren(pid))

    # Wait for our parent to finish initialization.
    lock.Wait()
    del lock

    # Create a process group for the grandchild so it can manage things
    # independent of the init process.
    os.setpgrp()

    # The grandchild will return and take over the rest of the sdk steps.
    return first_pid
    def run(self):
        """Runs the test in a proper environment (e.g. qemu)."""

        # We know these pre-tests are fast (especially if they've already been run
        # once), so run them automatically for the user if they test by hand.
        self.pre_test()

        for mount in self._BIND_MOUNT_PATHS:
            path = os.path.join(self.sysroot, mount)
            osutils.SafeMakedirs(path)
            osutils.Mount('/' + mount, path, 'none', osutils.MS_BIND)

        positive_filters = self.gtest_filter[0]
        negative_filters = self.gtest_filter[1]

        if self.user_gtest_filter:
            positive_filters += self.user_gtest_filter[0]
            negative_filters += self.user_gtest_filter[1]

        filters = (':'.join(positive_filters), ':'.join(negative_filters))
        gtest_filter = '%s-%s' % filters

        cmd = self.removeSysrootPrefix(self.bin)
        argv = self.args[:]
        argv[0] = self.removeSysrootPrefix(argv[0])
        if gtest_filter != '-':
            argv.append('--gtest_filter=' + gtest_filter)

        # Some programs expect to find data files via $CWD, so doing a chroot
        # and dropping them into / would make them fail.
        cwd = self.removeSysrootPrefix(os.getcwd())

        # Fork off a child to run the test.  This way we can make tweaks to the
        # env that only affect the child (gid/uid/chroot/cwd/etc...).  We have
        # to fork anyways to run the test, so might as well do it all ourselves
        # to avoid (slow) chaining through programs like:
        #   sudo -u $SUDO_UID -g $SUDO_GID chroot $SYSROOT bash -c 'cd $CWD; $BIN'
        child = os.fork()
        if child == 0:
            print('chroot: %s' % self.sysroot)
            print('cwd: %s' % cwd)
            print('cmd: {%s} %s' % (cmd, ' '.join(map(repr, argv))))
            os.chroot(self.sysroot)
            os.chdir(cwd)
            # The TERM the user is leveraging might not exist in the sysroot.
            # Force a sane default that supports standard color sequences.
            os.environ['TERM'] = 'ansi'
            # Some progs want this like bash else they get super confused.
            os.environ['PWD'] = cwd
            if not self.run_as_root:
                _, uid, gid, home = self.GetNonRootAccount()
                os.setgid(gid)
                os.setuid(uid)
                os.environ['HOME'] = home
            sys.exit(os.execvp(cmd, argv))

        proctitle.settitle('sysroot watcher', cmd)

        # Mask SIGINT with the assumption that the child will catch & process it.
        # We'll pass that back up below.
        signal.signal(signal.SIGINT, signal.SIG_IGN)
        _, status = os.waitpid(child, 0)

        failmsg = None
        if os.WIFSIGNALED(status):
            sig = os.WTERMSIG(status)
            failmsg = 'signal %s(%i)' % (signals.StrSignal(sig), sig)
        else:
            exit_status = os.WEXITSTATUS(status)
            if exit_status:
                failmsg = 'exit code %i' % exit_status
        if failmsg:
            print('Error: %s: failed with %s' % (cmd, failmsg),
                  file=sys.stderr)

        process_util.ExitAsStatus(status)