GH-87235: Make sure "python /dev/fd/9 9</path/to/script.py" works on macOS (#99768)

On macOS all file descriptors for a particular file in /dev/fd
share the same file offset, that is ``open("/dev/fd/9", "r")`` behaves
more like ``dup(9)`` than a regular open.

This causes problems when a user tries to run "/dev/fd/9" as a script
because zipimport changes the file offset to try to read a zipfile
directory. Therefore change zipimport to reset the file offset after
trying to read the zipfile directory.
This commit is contained in:
Ronald Oussoren 2022-11-27 11:56:14 +01:00 committed by GitHub
parent 9c9f085e9a
commit d08fb25769
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 123 additions and 101 deletions

View file

@ -752,6 +752,20 @@ class CmdLineTest(unittest.TestCase):
self.assertIn(": can't open file ", err)
self.assertNotEqual(proc.returncode, 0)
@unittest.skipUnless(os.path.exists('/dev/fd/0'), 'requires /dev/fd platform')
def test_script_as_dev_fd(self):
# GH-87235: On macOS passing a non-trivial script to /dev/fd/N can cause
# problems because all open /dev/fd/N file descriptors share the same
# offset.
script = 'print("12345678912345678912345")'
with os_helper.temp_dir() as work_dir:
script_name = _make_test_script(work_dir, 'script.py', script)
with open(script_name, "r") as fp:
p = spawn_python(f"/dev/fd/{fp.fileno()}", close_fds=False, pass_fds=(0,1,2,fp.fileno()))
out, err = p.communicate()
self.assertEqual(out, b"12345678912345678912345\n")
def tearDownModule():
support.reap_children()

View file

@ -347,6 +347,11 @@ def _read_directory(archive):
raise ZipImportError(f"can't open Zip file: {archive!r}", path=archive)
with fp:
# GH-87235: On macOS all file descriptors for /dev/fd/N share the same
# file offset, reset the file offset after scanning the zipfile diretory
# to not cause problems when some runs 'python3 /dev/fd/9 9<some_script'
start_offset = fp.tell()
try:
try:
fp.seek(-END_CENTRAL_DIR_SIZE, 2)
header_position = fp.tell()
@ -455,6 +460,8 @@ def _read_directory(archive):
t = (path, compress, data_size, file_size, file_offset, time, date, crc)
files[name] = t
count += 1
finally:
fp.seek(start_offset)
_bootstrap._verbose_message('zipimport: found {} names in {!r}', count, archive)
return files

View file

@ -0,0 +1 @@
On macOS ``python3 /dev/fd/9 9</path/to/script.py`` failed for any script longer than a couple of bytes.