Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Lib/os.py
Original file line number Diff line number Diff line change
Expand Up @@ -643,11 +643,13 @@ def _execvpe(file, args, env=None):
argrest = (args,)
env = environ

file = fspath(file)
if path.dirname(file):
exec_func(file, *argrest)
return
saved_exc = None
path_list = get_exec_path(env)
orig_file = file
if name != 'nt':
file = fsencode(file)
path_list = map(fsencode, path_list)
Expand All @@ -663,6 +665,11 @@ def _execvpe(file, args, env=None):
saved_exc = e
if saved_exc is not None:
raise saved_exc
# At this point, last_exc.filename contains the full path of whatever
# directory happened to be last in path_list. Set it to the filename that
# was passed in, which is what the caller will expect. This is what
# subprocess does too (see err_filename in Popen._execute_child()).
last_exc.filename = orig_file
raise last_exc


Expand Down
46 changes: 42 additions & 4 deletions Lib/test/test_os/test_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -2636,12 +2636,50 @@ def mock_execve(name, *args):

@unittest.skipUnless(hasattr(os, 'execv'),
"need os.execv()")
@unittest.skipIf(support.is_emscripten,
"Emscripten always fails with ENOEXEC")
@unittest.skipIf(support.is_android,
"PATH contains an inaccessible directory on Android")
class ExecTests(unittest.TestCase):
@unittest.skipIf(USING_LINUXTHREADS,
"avoid triggering a linuxthreads bug: see issue #4970")
def _test_bad_program(self, do_exec, exc_type=OSError):
bad_filenames = ['nosuchapp', FakePath('nosuchapp')]
if os.name != 'nt':
# Bytes program names are not supported on Windows.
bad_filenames += [b'nosuchapp', FakePath(b'nosuchapp')]
for bad_filename in bad_filenames:
with self.subTest(bad_filename):
with self.assertRaises(exc_type) as ctx:
do_exec(bad_filename)
self.assertEqual(ctx.exception.filename,
os.fspath(bad_filename))
self.assertIn('nosuchapp', str(ctx.exception))

@unittest.skipIf(USING_LINUXTHREADS, "linuxthreads bug: see issue #4970")
def test_execv_with_bad_program(self):
self._test_bad_program(lambda name: os.execv(name, ['nosuchapp']))

@unittest.skipIf(USING_LINUXTHREADS, "linuxthreads bug: see issue #4970")
def test_execvp_with_bad_program(self):
self._test_bad_program(lambda name: os.execvp(name, ['nosuchapp']))

@unittest.skipIf(USING_LINUXTHREADS, "linuxthreads bug: see issue #4970")
def test_execve_with_bad_program(self):
self._test_bad_program(lambda name: os.execve(name, ['nosuchapp'], {}))

@unittest.skipIf(USING_LINUXTHREADS, "linuxthreads bug: see issue #4970")
def test_execvpe_with_bad_program(self):
self.assertRaises(OSError, os.execvpe, 'no such app-',
['no such app-'], None)
self._test_bad_program(lambda name: os.execvpe(name, ['nosuchapp'], {}))

@unittest.skipUnless(os.name == 'posix', 'POSIX specific test')
@unittest.skipIf(USING_LINUXTHREADS, "linuxthreads bug: see issue #4970")
def test_execvp_with_bad_path_entry(self):
# A regular file in PATH makes the exec fail with ENOTDIR.
create_file(os_helper.TESTFN)
self.addCleanup(os_helper.unlink, os_helper.TESTFN)
with os_helper.EnvironmentVarGuard() as env:
env['PATH'] = os.path.abspath(os_helper.TESTFN)
self._test_bad_program(lambda name: os.execvp(name, ['nosuchapp']),
NotADirectoryError)

def test_execv_with_bad_arglist(self):
self.assertRaises(ValueError, os.execv, 'notepad', ())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
The :func:`os.exec\* <os.execl>` functions now set the
:attr:`~OSError.filename` attribute of the raised :exc:`FileNotFoundError`
or :exc:`NotADirectoryError` to the program name passed by the caller.
2 changes: 1 addition & 1 deletion Modules/posixmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -7520,7 +7520,7 @@ os_execv_impl(PyObject *module, path_t *path, PyObject *argv)

/* If we get here it's definitely an error */

posix_error();
posix_path_error(path);
free_string_array(argvlist, argc);
return NULL;
}
Expand Down
Loading