| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent d089cad commit 8b6eaef
3 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -183,19 +183,37 @@ class _ExecutableTarget: | |||
| 183 | 183 | ||
| 184 | 184 | class _ScriptTarget(_ExecutableTarget): | |
| 185 | 185 | def __init__(self, target): | |
| 186 | - self._target = os.path.realpath(target) | ||
| 186 | + self._check(target) | ||
| 187 | + self._target = self._safe_realpath(target) | ||
| 188 | + | ||
| 189 | + # If PYTHONSAFEPATH (-P) is not set, sys.path[0] is the directory | ||
| 190 | + # of pdb, and we should replace it with the directory of the script | ||
| 191 | + if not sys.flags.safe_path: | ||
| 192 | + sys.path[0] = os.path.dirname(self._target) | ||
| 187 | 193 | ||
| 188 | - if not os.path.exists(self._target): | ||
| 194 | + @staticmethod | ||
| 195 | + def _check(target): | ||
| 196 | + """ | ||
| 197 | + Check that target is plausibly a script. | ||
| 198 | + """ | ||
| 199 | + if not os.path.exists(target): | ||
| 189 | 200 | print(f'Error: {target} does not exist') | |
| 190 | 201 | sys.exit(1) | |
| 191 | - if os.path.isdir(self._target): | ||
| 202 | + if os.path.isdir(target): | ||
| 192 | 203 | print(f'Error: {target} is a directory') | |
| 193 | 204 | sys.exit(1) | |
| 194 | 205 | ||
| 195 | - # If safe_path(-P) is not set, sys.path[0] is the directory | ||
| 196 | - # of pdb, and we should replace it with the directory of the script | ||
| 197 | - if not sys.flags.safe_path: | ||
| 198 | - sys.path[0] = os.path.dirname(self._target) | ||
| 206 | + @staticmethod | ||
| 207 | + def _safe_realpath(path): | ||
| 208 | + """ | ||
| 209 | + Return the canonical path (realpath) if it is accessible from the userspace. | ||
| 210 | + Otherwise (for example, if the path is a symlink to an anonymous pipe), | ||
| 211 | + return the original path. | ||
| 212 | + | ||
| 213 | + See GH-142315. | ||
| 214 | + """ | ||
| 215 | + realpath = os.path.realpath(path) | ||
| 216 | + return realpath if os.path.exists(realpath) else path | ||
| 199 | 217 | ||
| 200 | 218 | def __repr__(self): | |
| 201 | 219 | return self._target | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -3561,6 +3561,24 @@ def _assert_find_function(self, file_content, func_name, expected): | |||
| 3561 | 3561 | self.assertEqual( | |
| 3562 | 3562 | expected, pdb.find_function(func_name, os_helper.TESTFN)) | |
| 3563 | 3563 | ||
| 3564 | + def _fd_dir_for_pipe_targets(self): | ||
| 3565 | + """Return a directory exposing live file descriptors, if any.""" | ||
| 3566 | + proc_fd = "/proc/self/fd" | ||
| 3567 | + if os.path.isdir(proc_fd) and os.path.exists(os.path.join(proc_fd, '0')): | ||
| 3568 | + return proc_fd | ||
| 3569 | + | ||
| 3570 | + dev_fd = "/dev/fd" | ||
| 3571 | + if os.path.isdir(dev_fd) and os.path.exists(os.path.join(dev_fd, '0')): | ||
| 3572 | + if sys.platform.startswith("freebsd"): | ||
| 3573 | + try: | ||
| 3574 | + if os.stat("/dev").st_dev == os.stat(dev_fd).st_dev: | ||
| 3575 | + return None | ||
| 3576 | + except FileNotFoundError: | ||
| 3577 | + return None | ||
| 3578 | + return dev_fd | ||
| 3579 | + | ||
| 3580 | + return None | ||
| 3581 | + | ||
| 3564 | 3582 | def test_find_function_empty_file(self): | |
| 3565 | 3583 | self._assert_find_function(b'', 'foo', None) | |
| 3566 | 3584 | ||
@@ -3619,6 +3637,47 @@ def test_spec(self): | |||
| 3619 | 3637 | stdout, _ = self.run_pdb_script(script, commands) | |
| 3620 | 3638 | self.assertIn('None', stdout) | |
| 3621 | 3639 | ||
| 3640 | + def test_script_target_anonymous_pipe(self): | ||
| 3641 | + """ | ||
| 3642 | + _ScriptTarget doesn't fail on an anonymous pipe. | ||
| 3643 | + | ||
| 3644 | + GH-142315 | ||
| 3645 | + """ | ||
| 3646 | + fd_dir = self._fd_dir_for_pipe_targets() | ||
| 3647 | + if fd_dir is None: | ||
| 3648 | + self.skipTest('anonymous pipe targets require /proc/self/fd or /dev/fd') | ||
| 3649 | + | ||
| 3650 | + read_fd, write_fd = os.pipe() | ||
| 3651 | + | ||
| 3652 | + def safe_close(fd): | ||
| 3653 | + try: | ||
| 3654 | + os.close(fd) | ||
| 3655 | + except OSError: | ||
| 3656 | + pass | ||
| 3657 | + | ||
| 3658 | + self.addCleanup(safe_close, read_fd) | ||
| 3659 | + self.addCleanup(safe_close, write_fd) | ||
| 3660 | + | ||
| 3661 | + pipe_path = os.path.join(fd_dir, str(read_fd)) | ||
| 3662 | + if not os.path.exists(pipe_path): | ||
| 3663 | + self.skipTest('fd directory does not expose anonymous pipes') | ||
| 3664 | + | ||
| 3665 | + script_source = 'marker = "via_pipe"\n' | ||
| 3666 | + os.write(write_fd, script_source.encode('utf-8')) | ||
| 3667 | + os.close(write_fd) | ||
| 3668 | + | ||
| 3669 | + original_path0 = sys.path[0] | ||
| 3670 | + self.addCleanup(sys.path.__setitem__, 0, original_path0) | ||
| 3671 | + | ||
| 3672 | + target = pdb._ScriptTarget(pipe_path) | ||
| 3673 | + code_text = target.code | ||
| 3674 | + namespace = target.namespace | ||
| 3675 | + exec(code_text, namespace) | ||
| 3676 | + | ||
| 3677 | + self.assertEqual(namespace['marker'], 'via_pipe') | ||
| 3678 | + self.assertEqual(namespace['__file__'], target.filename) | ||
| 3679 | + self.assertIsNone(namespace['__spec__']) | ||
| 3680 | + | ||
| 3622 | 3681 | def test_find_function_first_executable_line(self): | |
| 3623 | 3682 | code = textwrap.dedent("""\ | |
| 3624 | 3683 | def foo(): pass | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,2 @@ | |||
| 1 | + Pdb can now run scripts from anonymous pipes used in process substitution. | ||
| 2 | + Patch by Bartosz Sławecki. | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments