Ejemplo n.º 1
0
  def testShouldHijack(self):
    self.assertEqual(
        False, match.ShouldHijack('# comment\n[line 2]'))
    self.assertEqual(
        False, match.ShouldHijack('#!/usr/bin/python\n'))
    self.assertEqual(
        False, match.ShouldHijack(''))
    self.assertEqual(
        False, match.ShouldHijack('\n'))

    self.assertEqual(
        True, match.ShouldHijack('#!/usr/bin/env bash\n'))

    self.assertEqual(
        True, match.ShouldHijack('#!/bin/bash\n[line 2]'))
    self.assertEqual(
        True, match.ShouldHijack('#!/bin/bash -e\n[line 2]'))
    self.assertEqual(
        True, match.ShouldHijack('#!/bin/sh\n[line 2]\n'))
    self.assertEqual(
        True, match.ShouldHijack('#!/bin/sh -e\n[line 2]\n'))

    # Unlikely but OK
    self.assertEqual(
        True, match.ShouldHijack('#!/usr/bin/env sh\n'))
    def _Exec(self, argv0_path, argv, argv0_spid, environ, should_retry):
        # type: (str, List[str], int, Dict[str, str], bool) -> None
        if self.hijack_shebang:
            try:
                f = self.fd_state.Open(argv0_path)
            except OSError as e:
                pass
            else:
                try:
                    # Test if the shebang looks like a shell.  The file might be binary
                    # with no newlines, so read 80 bytes instead of readline().
                    line = f.read(80)  # type: ignore  # TODO: fix this
                    if match.ShouldHijack(line):
                        argv = [self.hijack_shebang, argv0_path] + argv[1:]
                        argv0_path = self.hijack_shebang
                        self.debug_f.log('Hijacked: %s', argv)
                    else:
                        #self.debug_f.log('Not hijacking %s (%r)', argv, line)
                        pass
                finally:
                    f.close()

        # TODO: If there is an error, like the file isn't executable, then we should
        # exit, and the parent will reap it.  Should it capture stderr?

        try:
            posix.execve(argv0_path, argv, environ)
        except OSError as e:
            # Run with /bin/sh when ENOEXEC error (no shebang).  All shells do this.
            if e.errno == errno.ENOEXEC and should_retry:
                new_argv = ['/bin/sh', argv0_path]
                new_argv.extend(argv[1:])
                self._Exec('/bin/sh', new_argv, argv0_spid, environ, False)
                # NO RETURN

            # Would be nice: when the path is relative and ENOENT: print PWD and do
            # spelling correction?

            self.errfmt.Print("Can't execute %r: %s",
                              argv0_path,
                              posix.strerror(e.errno),
                              span_id=argv0_spid)

            # POSIX mentions 126 and 127 for two specific errors.  The rest are
            # unspecified.
            #
            # http://pubs.opengroup.org/onlinepubs/9699919799.2016edition/utilities/V3_chap02.html#tag_18_08_02
            if e.errno == errno.EACCES:
                status = 126
            elif e.errno == errno.ENOENT:
                # TODO: most shells print 'command not found', rather than strerror()
                # == "No such file or directory".  That's better because it's at the
                # end of the path search, and we're never searching for a directory.
                status = 127
            else:
                # dash uses 2, but we use that for parse errors.  This seems to be
                # consistent with mksh and zsh.
                status = 127

            sys.exit(status)  # raises SystemExit