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