Merge pull request #6958 from youknowone/os-posix

Update os from v3.14.2 and fix nt,posix,_os
This commit is contained in:
Jeong, YunWon authored and GitHub committed 2026-02-03 23:00:25 +09:00
commit 9f0cd323b6
15 files changed
+804 -399

No files matched your search

Vendored
+61 -54
View File
@@ -10,7 +10,7 @@ This exports:
- os.extsep is the extension separator (always '.')
- os.altsep is the alternate pathname separator (None or '/')
- os.pathsep is the component separator used in $PATH etc
- os.linesep is the line separator in text files ('\r' or '\n' or '\r\n')
- os.linesep is the line separator in text files ('\n' or '\r\n')
- os.defpath is the default search path for executables
- os.devnull is the file path of the null device ('/dev/null', etc.)
@@ -64,6 +64,10 @@ if 'posix' in _names:
from posix import _have_functions
except ImportError:
pass
try:
from posix import _create_environ
except ImportError:
pass
import posix
__all__.extend(_get_exports_list(posix))
@@ -88,6 +92,10 @@ elif 'nt' in _names:
from nt import _have_functions
except ImportError:
pass
try:
from nt import _create_environ
except ImportError:
pass
else:
raise ImportError('no os specific module found')
@@ -366,61 +374,45 @@ def walk(top, topdown=True, onerror=None, followlinks=False):
# minor reason when (say) a thousand readable directories are still
# left to visit.
try:
scandir_it = scandir(top)
with scandir(top) as entries:
for entry in entries:
try:
if followlinks is _walk_symlinks_as_files:
is_dir = entry.is_dir(follow_symlinks=False) and not entry.is_junction()
else:
is_dir = entry.is_dir()
except OSError:
# If is_dir() raises an OSError, consider the entry not to
# be a directory, same behaviour as os.path.isdir().
is_dir = False
if is_dir:
dirs.append(entry.name)
else:
nondirs.append(entry.name)
if not topdown and is_dir:
# Bottom-up: traverse into sub-directory, but exclude
# symlinks to directories if followlinks is False
if followlinks:
walk_into = True
else:
try:
is_symlink = entry.is_symlink()
except OSError:
# If is_symlink() raises an OSError, consider the
# entry not to be a symbolic link, same behaviour
# as os.path.islink().
is_symlink = False
walk_into = not is_symlink
if walk_into:
walk_dirs.append(entry.path)
except OSError as error:
if onerror is not None:
onerror(error)
continue
cont = False
with scandir_it:
while True:
try:
try:
entry = next(scandir_it)
except StopIteration:
break
except OSError as error:
if onerror is not None:
onerror(error)
cont = True
break
try:
if followlinks is _walk_symlinks_as_files:
is_dir = entry.is_dir(follow_symlinks=False) and not entry.is_junction()
else:
is_dir = entry.is_dir()
except OSError:
# If is_dir() raises an OSError, consider the entry not to
# be a directory, same behaviour as os.path.isdir().
is_dir = False
if is_dir:
dirs.append(entry.name)
else:
nondirs.append(entry.name)
if not topdown and is_dir:
# Bottom-up: traverse into sub-directory, but exclude
# symlinks to directories if followlinks is False
if followlinks:
walk_into = True
else:
try:
is_symlink = entry.is_symlink()
except OSError:
# If is_symlink() raises an OSError, consider the
# entry not to be a symbolic link, same behaviour
# as os.path.islink().
is_symlink = False
walk_into = not is_symlink
if walk_into:
walk_dirs.append(entry.path)
if cont:
continue
if topdown:
# Yield before sub-directory traversal if going top down
yield top, dirs, nondirs
@@ -774,7 +766,7 @@ class _Environ(MutableMapping):
new.update(self)
return new
def _createenviron():
def _create_environ_mapping():
if name == 'nt':
# Where Env Var Names Must Be UPPERCASE
def check_str(value):
@@ -804,10 +796,25 @@ def _createenviron():
encode, decode)
# unicode environ
environ = _createenviron()
del _createenviron
environ = _create_environ_mapping()
del _create_environ_mapping
if _exists("_create_environ"):
def reload_environ():
data = _create_environ()
if name == 'nt':
encodekey = environ.encodekey
data = {encodekey(key): value
for key, value in data.items()}
# modify in-place to keep os.environb in sync
env_data = environ._data
env_data.clear()
env_data.update(data)
__all__.append("reload_environ")
def getenv(key, default=None):
"""Get an environment variable, return None if it doesn't exist.
The optional second argument can specify an alternate default.
-1
View File
@@ -170,7 +170,6 @@ class GenericTest:
os.close(w)
self.assertFalse(self.pathmodule.exists(r))
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_exists_bool(self):
for fd in False, True:
with self.assertWarnsRegex(RuntimeWarning,
+223 -80
View File
@@ -104,7 +104,7 @@ requires_splice_pipe = unittest.skipIf(sys.platform.startswith("aix"),
def tearDownModule():
asyncio.set_event_loop_policy(None)
asyncio.events._set_event_loop_policy(None)
class MiscTests(unittest.TestCase):
@@ -187,10 +187,6 @@ class FileTests(unittest.TestCase):
os.close(f)
self.assertTrue(os.access(os_helper.TESTFN, os.W_OK))
@unittest.skipIf(sys.platform == 'win32', "TODO: RUSTPYTHON; BrokenPipeError: (32, 'The process cannot access the file because it is being used by another process. (os error 32)')")
@unittest.skipIf(
support.is_emscripten, "Test is unstable under Emscripten."
)
@unittest.skipIf(
support.is_wasi, "WASI does not support dup."
)
@@ -233,6 +229,94 @@ class FileTests(unittest.TestCase):
self.assertEqual(type(s), bytes)
self.assertEqual(s, b"spam")
def test_readinto(self):
with open(os_helper.TESTFN, "w+b") as fobj:
fobj.write(b"spam")
fobj.flush()
fd = fobj.fileno()
os.lseek(fd, 0, 0)
# Oversized so readinto without hitting end.
buffer = bytearray(7)
s = os.readinto(fd, buffer)
self.assertEqual(type(s), int)
self.assertEqual(s, 4)
# Should overwrite the first 4 bytes of the buffer.
self.assertEqual(buffer[:4], b"spam")
# Readinto at EOF should return 0 and not touch buffer.
buffer[:] = b"notspam"
s = os.readinto(fd, buffer)
self.assertEqual(type(s), int)
self.assertEqual(s, 0)
self.assertEqual(bytes(buffer), b"notspam")
s = os.readinto(fd, buffer)
self.assertEqual(s, 0)
self.assertEqual(bytes(buffer), b"notspam")
# Readinto a 0 length bytearray when at EOF should return 0
self.assertEqual(os.readinto(fd, bytearray()), 0)
# Readinto a 0 length bytearray with data available should return 0.
os.lseek(fd, 0, 0)
self.assertEqual(os.readinto(fd, bytearray()), 0)
@unittest.skipUnless(hasattr(os, 'get_blocking'),
'needs os.get_blocking() and os.set_blocking()')
@unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()")
@unittest.skipIf(support.is_emscripten, "set_blocking does not work correctly")
def test_readinto_non_blocking(self):
# Verify behavior of a readinto which would block on a non-blocking fd.
r, w = os.pipe()
try:
os.set_blocking(r, False)
with self.assertRaises(BlockingIOError):
os.readinto(r, bytearray(5))
# Pass some data through
os.write(w, b"spam")
self.assertEqual(os.readinto(r, bytearray(4)), 4)
# Still don't block or return 0.
with self.assertRaises(BlockingIOError):
os.readinto(r, bytearray(5))
# At EOF should return size 0
os.close(w)
w = None
self.assertEqual(os.readinto(r, bytearray(5)), 0)
self.assertEqual(os.readinto(r, bytearray(5)), 0) # Still EOF
finally:
os.close(r)
if w is not None:
os.close(w)
def test_readinto_badarg(self):
with open(os_helper.TESTFN, "w+b") as fobj:
fobj.write(b"spam")
fobj.flush()
fd = fobj.fileno()
os.lseek(fd, 0, 0)
for bad_arg in ("test", bytes(), 14):
with self.subTest(f"bad buffer {type(bad_arg)}"):
with self.assertRaises(TypeError):
os.readinto(fd, bad_arg)
with self.subTest("doesn't work on file objects"):
with self.assertRaises(TypeError):
os.readinto(fobj, bytearray(5))
# takes two args
with self.assertRaises(TypeError):
os.readinto(fd)
# No data should have been read with the bad arguments.
buffer = bytearray(4)
s = os.readinto(fd, buffer)
self.assertEqual(s, 4)
self.assertEqual(buffer, b"spam")
@support.cpython_only
# Skip the test on 32-bit platforms: the number of bytes must fit in a
# Py_ssize_t type
@@ -252,6 +336,29 @@ class FileTests(unittest.TestCase):
# operating system is free to return less bytes than requested.
self.assertEqual(data, b'test')
@support.cpython_only
# Skip the test on 32-bit platforms: the number of bytes must fit in a
# Py_ssize_t type
@unittest.skipUnless(INT_MAX < PY_SSIZE_T_MAX,
"needs INT_MAX < PY_SSIZE_T_MAX")
@support.bigmemtest(size=INT_MAX + 10, memuse=1, dry_run=False)
def test_large_readinto(self, size):
self.addCleanup(os_helper.unlink, os_helper.TESTFN)
create_file(os_helper.TESTFN, b'test')
# Issue #21932: For readinto the buffer contains the length rather than
# a length being passed explicitly to read, should still get capped to a
# valid size / not raise an OverflowError for sizes larger than INT_MAX.
buffer = bytearray(INT_MAX + 10)
with open(os_helper.TESTFN, "rb") as fp:
length = os.readinto(fp.fileno(), buffer)
# The test does not try to read more than 2 GiB at once because the
# operating system is free to return less bytes than requested.
self.assertEqual(length, 4)
self.assertEqual(buffer[:4], b'test')
def test_write(self):
# os.write() accepts bytes- and buffer-like objects but not strings
fd = os.open(os_helper.TESTFN, os.O_CREAT | os.O_WRONLY)
@@ -710,7 +817,7 @@ class StatAttributeTests(unittest.TestCase):
self.assertEqual(ctx.exception.errno, errno.EBADF)
def check_file_attributes(self, result):
self.assertTrue(hasattr(result, 'st_file_attributes'))
self.assertHasAttr(result, 'st_file_attributes')
self.assertTrue(isinstance(result.st_file_attributes, int))
self.assertTrue(0 <= result.st_file_attributes <= 0xFFFFFFFF)
@@ -805,14 +912,28 @@ class UtimeTests(unittest.TestCase):
set_time(filename, (atime_ns, mtime_ns))
st = os.stat(filename)
if support_subsecond:
self.assertAlmostEqual(st.st_atime, atime_ns * 1e-9, delta=1e-6)
self.assertAlmostEqual(st.st_mtime, mtime_ns * 1e-9, delta=1e-6)
if support.is_emscripten:
# Emscripten timestamps are roundtripped through a 53 bit integer of
# nanoseconds. If we want to represent ~50 years which is an 11
# digits number of seconds:
# 2*log10(60) + log10(24) + log10(365) + log10(60) + log10(50)
# is about 11. Because 53 * log10(2) is about 16, we only have 5
# digits worth of sub-second precision.
# Some day it would be good to fix this upstream.
delta=1e-5
self.assertAlmostEqual(st.st_atime, atime_ns * 1e-9, delta=1e-5)
self.assertAlmostEqual(st.st_mtime, mtime_ns * 1e-9, delta=1e-5)
self.assertAlmostEqual(st.st_atime_ns, atime_ns, delta=1e9 * 1e-5)
self.assertAlmostEqual(st.st_mtime_ns, mtime_ns, delta=1e9 * 1e-5)
else:
self.assertEqual(st.st_atime, atime_ns * 1e-9)
self.assertEqual(st.st_mtime, mtime_ns * 1e-9)
self.assertEqual(st.st_atime_ns, atime_ns)
self.assertEqual(st.st_mtime_ns, mtime_ns)
if support_subsecond:
self.assertAlmostEqual(st.st_atime, atime_ns * 1e-9, delta=1e-6)
self.assertAlmostEqual(st.st_mtime, mtime_ns * 1e-9, delta=1e-6)
else:
self.assertEqual(st.st_atime, atime_ns * 1e-9)
self.assertEqual(st.st_mtime, mtime_ns * 1e-9)
self.assertEqual(st.st_atime_ns, atime_ns)
self.assertEqual(st.st_mtime_ns, mtime_ns)
def test_utime(self):
def set_time(filename, ns):
@@ -825,9 +946,7 @@ class UtimeTests(unittest.TestCase):
# Convert a number of nanosecond (int) to a number of seconds (float).
# Round towards infinity by adding 0.5 nanosecond to avoid rounding
# issue, os.utime() rounds towards minus infinity.
# XXX: RUSTPYTHON os.utime() use `[Duration::from_secs_f64](https://doc.rust-lang.org/std/time/struct.Duration.html#method.try_from_secs_f64)`
# return (ns * 1e-9) + 0.5e-9
return (ns * 1e-9)
return (ns * 1e-9) + 0.5e-9
def test_utime_by_indexed(self):
# pass times as floating-point seconds as the second indexed parameter
@@ -1300,6 +1419,52 @@ class EnvironTests(mapping_tests.BasicTestMappingProtocol):
self._test_underlying_process_env('_A_', '')
self._test_underlying_process_env(overridden_key, original_value)
def test_reload_environ(self):
# Test os.reload_environ()
has_environb = hasattr(os, 'environb')
# Test with putenv() which doesn't update os.environ
os.environ['test_env'] = 'python_value'
os.putenv("test_env", "new_value")
self.assertEqual(os.environ['test_env'], 'python_value')
if has_environb:
self.assertEqual(os.environb[b'test_env'], b'python_value')
os.reload_environ()
self.assertEqual(os.environ['test_env'], 'new_value')
if has_environb:
self.assertEqual(os.environb[b'test_env'], b'new_value')
# Test with unsetenv() which doesn't update os.environ
os.unsetenv('test_env')
self.assertEqual(os.environ['test_env'], 'new_value')
if has_environb:
self.assertEqual(os.environb[b'test_env'], b'new_value')
os.reload_environ()
self.assertNotIn('test_env', os.environ)
if has_environb:
self.assertNotIn(b'test_env', os.environb)
if has_environb:
# test reload_environ() on os.environb with putenv()
os.environb[b'test_env'] = b'python_value2'
os.putenv("test_env", "new_value2")
self.assertEqual(os.environb[b'test_env'], b'python_value2')
self.assertEqual(os.environ['test_env'], 'python_value2')
os.reload_environ()
self.assertEqual(os.environb[b'test_env'], b'new_value2')
self.assertEqual(os.environ['test_env'], 'new_value2')
# test reload_environ() on os.environb with unsetenv()
os.unsetenv('test_env')
self.assertEqual(os.environb[b'test_env'], b'new_value2')
self.assertEqual(os.environ['test_env'], 'new_value2')
os.reload_environ()
self.assertNotIn(b'test_env', os.environb)
self.assertNotIn('test_env', os.environ)
class WalkTests(unittest.TestCase):
"""Tests for os.walk()."""
@@ -1370,9 +1535,7 @@ class WalkTests(unittest.TestCase):
else:
self.sub2_tree = (sub2_path, ["SUB21"], ["tmp3"])
if not support.is_emscripten:
# Emscripten fails with inaccessible directory
os.chmod(sub21_path, 0)
os.chmod(sub21_path, 0)
try:
os.listdir(sub21_path)
except PermissionError:
@@ -1668,9 +1831,6 @@ class FwalkTests(WalkTests):
# check that listdir() returns consistent information
self.assertEqual(set(os.listdir(rootfd)), set(dirs) | set(files))
@unittest.skipIf(
support.is_emscripten, "Cannot dup stdout on Emscripten"
)
@unittest.skipIf(
support.is_android, "dup return value is unpredictable on Android"
)
@@ -1687,9 +1847,6 @@ class FwalkTests(WalkTests):
self.addCleanup(os.close, newfd)
self.assertEqual(newfd, minfd)
@unittest.skipIf(
support.is_emscripten, "Cannot dup stdout on Emscripten"
)
@unittest.skipIf(
support.is_android, "dup return value is unpredictable on Android"
)
@@ -1725,17 +1882,6 @@ class BytesWalkTests(WalkTests):
bdirs[:] = list(map(os.fsencode, dirs))
bfiles[:] = list(map(os.fsencode, files))
@unittest.expectedFailure # TODO: RUSTPYTHON; WalkTests doesn't have these methods
def test_compare_to_walk(self):
return super().test_compare_to_walk()
@unittest.expectedFailure # TODO: RUSTPYTHON; WalkTests doesn't have these methods
def test_dir_fd(self):
return super().test_dir_fd()
@unittest.expectedFailure # TODO: RUSTPYTHON; WalkTests doesn't have these methods
def test_yields_correct_dir_fd(self):
return super().test_yields_correct_dir_fd()
@unittest.skipUnless(hasattr(os, 'fwalk'), "Test needs os.fwalk()")
class BytesFwalkTests(FwalkTests):
@@ -1770,10 +1916,12 @@ class MakedirTests(unittest.TestCase):
os.makedirs(path)
@unittest.skipIf(
support.is_emscripten or support.is_wasi,
"Emscripten's/WASI's umask is a stub."
support.is_wasi,
"WASI's umask is a stub."
)
def test_mode(self):
# Note: in some cases, the umask might already be 2 in which case this
# will pass even if os.umask is actually broken.
with os_helper.temp_umask(0o002):
base = os_helper.TESTFN
parent = os.path.join(base, 'dir1')
@@ -1786,8 +1934,8 @@ class MakedirTests(unittest.TestCase):
self.assertEqual(os.stat(parent).st_mode & 0o777, 0o775)
@unittest.skipIf(
support.is_emscripten or support.is_wasi,
"Emscripten's/WASI's umask is a stub."
support.is_wasi,
"WASI's umask is a stub."
)
def test_exist_ok_existing_directory(self):
path = os.path.join(os_helper.TESTFN, 'dir1')
@@ -1804,8 +1952,8 @@ class MakedirTests(unittest.TestCase):
os.makedirs(os.path.abspath('/'), exist_ok=True)
@unittest.skipIf(
support.is_emscripten or support.is_wasi,
"Emscripten's/WASI's umask is a stub."
support.is_wasi,
"WASI's umask is a stub."
)
def test_exist_ok_s_isgid_directory(self):
path = os.path.join(os_helper.TESTFN, 'dir1')
@@ -2035,7 +2183,7 @@ class GetRandomTests(unittest.TestCase):
self.assertEqual(empty, b'')
def test_getrandom_random(self):
self.assertTrue(hasattr(os, 'GRND_RANDOM'))
self.assertHasAttr(os, 'GRND_RANDOM')
# Don't test os.getrandom(1, os.GRND_RANDOM) to not consume the rare
# resource /dev/random
@@ -2319,9 +2467,13 @@ class Win32ErrorTests(unittest.TestCase):
@unittest.skipIf(support.is_wasi, "Cannot create invalid FD on WASI.")
class TestInvalidFD(unittest.TestCase):
singles = ["fchdir", "dup", "fdatasync", "fstat",
"fstatvfs", "fsync", "tcgetpgrp", "ttyname"]
singles_fildes = {"fchdir", "fdatasync", "fsync"}
singles = ["fchdir", "dup", "fstat", "fstatvfs", "tcgetpgrp", "ttyname"]
singles_fildes = {"fchdir"}
# systemd-nspawn --suppress-sync=true does not verify fd passed
# fdatasync() and fsync(), and always returns success
if not support.in_systemd_nspawn_sync_suppressed():
singles += ["fdatasync", "fsync"]
singles_fildes |= {"fdatasync", "fsync"}
#singles.append("close")
#We omit close because it doesn't raise an exception on some platforms
def get_single(f):
@@ -2379,10 +2531,6 @@ class TestInvalidFD(unittest.TestCase):
self.check(os.dup2, 20)
@unittest.skipUnless(hasattr(os, 'dup2'), 'test needs os.dup2()')
@unittest.skipIf(
support.is_emscripten,
"dup2() with negative fds is broken on Emscripten (see gh-102179)"
)
def test_dup2_negative_fd(self):
valid_fd = os.open(__file__, os.O_RDONLY)
self.addCleanup(os.close, valid_fd)
@@ -2406,20 +2554,21 @@ class TestInvalidFD(unittest.TestCase):
def test_fchown(self):
self.check(os.fchown, -1, -1)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.skipUnless(hasattr(os, 'fpathconf'), 'test needs os.fpathconf()')
@unittest.skipIf(
support.is_emscripten or support.is_wasi,
"musl libc issue on Emscripten/WASI, bpo-46390"
)
def test_fpathconf(self):
self.assertIn("PC_NAME_MAX", os.pathconf_names)
self.check(os.pathconf, "PC_NAME_MAX")
self.check(os.fpathconf, "PC_NAME_MAX")
self.check_bool(os.pathconf, "PC_NAME_MAX")
self.check_bool(os.fpathconf, "PC_NAME_MAX")
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.skipUnless(hasattr(os, 'fpathconf'), 'test needs os.fpathconf()')
@unittest.skipIf(
support.linked_to_musl(),
'musl pathconf ignores the file descriptor and returns a constant',
)
def test_fpathconf_bad_fd(self):
self.check(os.pathconf, "PC_NAME_MAX")
self.check(os.fpathconf, "PC_NAME_MAX")
@unittest.skipUnless(hasattr(os, 'ftruncate'), 'test needs os.ftruncate()')
def test_ftruncate(self):
self.check(os.truncate, 0)
@@ -2434,6 +2583,10 @@ class TestInvalidFD(unittest.TestCase):
def test_read(self):
self.check(os.read, 1)
@unittest.skipUnless(hasattr(os, 'readinto'), 'test needs os.readinto()')
def test_readinto(self):
self.check(os.readinto, bytearray(5))
@unittest.skipUnless(hasattr(os, 'readv'), 'test needs os.readv()')
def test_readv(self):
buf = bytearray(10)
@@ -2462,13 +2615,8 @@ class TestInvalidFD(unittest.TestCase):
self.check(os.get_blocking)
self.check(os.set_blocking, True)
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_fchdir(self):
return super().test_fchdir()
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_fsync(self):
return super().test_fsync()
@unittest.skipUnless(hasattr(os, 'link'), 'requires os.link')
@@ -3355,9 +3503,6 @@ class DeviceEncodingTests(unittest.TestCase):
@unittest.skipUnless(os.isatty(0) and not win32_is_iot() and (sys.platform.startswith('win') or
(hasattr(locale, 'nl_langinfo') and hasattr(locale, 'CODESET'))),
'test requires a tty and either Windows or nl_langinfo(CODESET)')
@unittest.skipIf(
support.is_emscripten, "Cannot get encoding of stdin on Emscripten"
)
def test_device_encoding(self):
encoding = os.device_encoding(0)
self.assertIsNotNone(encoding)
@@ -3485,8 +3630,8 @@ class SpawnTests(unittest.TestCase):
exitcode = os.spawnl(os.P_WAIT, program, *args)
self.assertEqual(exitcode, self.exitcode)
@unittest.skipIf(sys.platform == "win32", "TODO: RUSTPYTHON; fix spawnve on Windows")
@requires_os_func('spawnle')
@unittest.skipIf(sys.platform == 'win32', "TODO: RUSTPYTHON; fix spawnve on Windows")
def test_spawnle(self):
program, args = self.create_args(with_env=True)
exitcode = os.spawnle(os.P_WAIT, program, *args, self.env)
@@ -3514,8 +3659,8 @@ class SpawnTests(unittest.TestCase):
exitcode = os.spawnv(os.P_WAIT, FakePath(program), args)
self.assertEqual(exitcode, self.exitcode)
@unittest.skipIf(sys.platform == "win32", "TODO: RUSTPYTHON; fix spawnve on Windows")
@requires_os_func('spawnve')
@unittest.skipIf(sys.platform == 'win32', "TODO: RUSTPYTHON; fix spawnve on Windows")
def test_spawnve(self):
program, args = self.create_args(with_env=True)
exitcode = os.spawnve(os.P_WAIT, program, args, self.env)
@@ -3539,7 +3684,6 @@ class SpawnTests(unittest.TestCase):
pid = os.spawnv(os.P_NOWAIT, program, args)
support.wait_process(pid, exitcode=self.exitcode)
@unittest.expectedFailure # TODO: RUSTPYTHON; fix spawnv bytes
@requires_os_func('spawnve')
def test_spawnve_bytes(self):
# Test bytes handling in parse_arglist and parse_envlist (#28114)
@@ -3623,8 +3767,8 @@ class SpawnTests(unittest.TestCase):
exitcode = spawn(os.P_WAIT, program, args, newenv)
self.assertEqual(exitcode, 0)
@unittest.skipIf(sys.platform == "win32", "TODO: RUSTPYTHON; fix spawnve on Windows")
@requires_os_func('spawnve')
@unittest.skipIf(sys.platform == 'win32', "TODO: RUSTPYTHON; fix spawnve on Windows")
def test_spawnve_invalid_env(self):
self._test_invalid_env(os.spawnve)
@@ -4989,7 +5133,7 @@ class TestScandir(unittest.TestCase):
entry_lstat,
os.name == 'nt')
@unittest.skipIf(sys.platform == 'linux', 'TODO: RUSTPYTHON; flaky test')
@unittest.skipIf(sys.platform == "linux", "TODO: RUSTPYTHON; flaky test")
def test_attributes(self):
link = os_helper.can_hardlink()
symlink = os_helper.can_symlink()
@@ -5171,7 +5315,7 @@ class TestScandir(unittest.TestCase):
with self.assertRaises(TypeError):
os.scandir(path_bytes)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: <builtin_function_or_method object at 0xba3106920> not found in {<builtin_function_or_method object at 0xba31078e0>, <builtin_function_or_method object at 0xba31079c0>, <builtin_function_or_method object at 0xba3107b10>, <builtin_function_or_method object at 0xba3159500>, <builtin_function_or_method object at 0xba3159570>, <builtin_function_or_method object at 0xba3107800>, <builtin_function_or_method object at 0xba3106760>, <builtin_function_or_method object at 0xba3106a00>, <builtin_function_or_method object at 0xba3106990>, <builtin_function_or_method object at 0xba3107330>, <builtin_function_or_method object at 0xba31072c0>, <builtin_function_or_method object at 0xba31064c0>}
@unittest.skipUnless(os.listdir in os.supports_fd,
'fd support for listdir required for this test.')
def test_fd(self):
@@ -5253,7 +5397,6 @@ class TestScandir(unittest.TestCase):
with self.check_no_resource_warning():
del iterator
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_resource_warning(self):
self.create_file("file.txt")
self.create_file("file2.txt")
@@ -5293,8 +5436,8 @@ class TestPEP519(unittest.TestCase):
def test_pathlike(self):
self.assertEqual('#feelthegil', self.fspath(FakePath('#feelthegil')))
self.assertTrue(issubclass(FakePath, os.PathLike))
self.assertTrue(isinstance(FakePath('x'), os.PathLike))
self.assertIsSubclass(FakePath, os.PathLike)
self.assertIsInstance(FakePath('x'), os.PathLike)
def test_garbage_in_exception_out(self):
vapor = type('blah', (), {})
@@ -5320,8 +5463,8 @@ class TestPEP519(unittest.TestCase):
# true on abstract implementation.
class A(os.PathLike):
pass
self.assertFalse(issubclass(FakePath, A))
self.assertTrue(issubclass(FakePath, os.PathLike))
self.assertNotIsSubclass(FakePath, A)
self.assertIsSubclass(FakePath, os.PathLike)
def test_pathlike_class_getitem(self):
self.assertIsInstance(os.PathLike[bytes], types.GenericAlias)
@@ -5331,7 +5474,7 @@ class TestPEP519(unittest.TestCase):
__slots__ = ()
def __fspath__(self):
return ''
self.assertFalse(hasattr(A(), '__dict__'))
self.assertNotHasAttr(A(), '__dict__')
def test_fspath_set_to_None(self):
class Foo:
@@ -5435,7 +5578,7 @@ class ForkTests(unittest.TestCase):
self.assertEqual(err.decode("utf-8"), "")
self.assertEqual(out.decode("utf-8"), "")
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: b"can't fork at interpreter shutdown" not found in b"Exception ignored in: <function AtFinalization.__del__ at 0xc508b30c0>\nAttributeError: 'NoneType' object has no attribute 'fork'\n"
def test_fork_at_finalization(self):
code = """if 1:
import atexit
+9 -2
View File
@@ -57,14 +57,21 @@ class PopenTest(unittest.TestCase):
def test_contextmanager(self):
with os.popen("echo hello") as f:
self.assertEqual(f.read(), "hello\n")
self.assertFalse(f.closed)
self.assertTrue(f.closed)
def test_iterating(self):
with os.popen("echo hello") as f:
self.assertEqual(list(f), ["hello\n"])
self.assertFalse(f.closed)
self.assertTrue(f.closed)
def test_keywords(self):
with os.popen(cmd="exit 0", mode="w", buffering=-1):
pass
with os.popen(cmd="echo hello", mode="r", buffering=-1) as f:
self.assertEqual(f.read(), "hello\n")
self.assertFalse(f.closed)
self.assertTrue(f.closed)
if __name__ == "__main__":
unittest.main()
+66 -15
View File
@@ -583,7 +583,6 @@ class PosixTester(unittest.TestCase):
self.assertGreater(len(path), 0)
self.assertEqual(posix.confstr(posix.confstr_names["CS_PATH"]), path)
@unittest.expectedFailureIf(sys.platform in ('darwin', 'linux'), '''TODO: RUSTPYTHON; AssertionError: "configuration names must be strings or integers" does not match "Expected type 'str' but 'float' found."''')
@unittest.skipUnless(hasattr(posix, 'sysconf'),
'test needs posix.sysconf()')
def test_sysconf(self):
@@ -1018,7 +1017,7 @@ class PosixTester(unittest.TestCase):
target = self.tempdir()
self.check_chmod(posix.chmod, target)
@unittest.skipIf(sys.platform in ('darwin', 'linux'), 'TODO: RUSTPYTHON; crash')
@unittest.skipIf(sys.platform in ("darwin", "linux"), "TODO: RUSTPYTHON; crash")
@os_helper.skip_unless_working_chmod
def test_fchmod_file(self):
with open(os_helper.TESTFN, 'wb+') as f:
@@ -1075,7 +1074,7 @@ class PosixTester(unittest.TestCase):
self.check_chmod_link(posix.chmod, target, link)
self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True)
@unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON; flaky')
@unittest.skipIf(sys.platform == "win32", "TODO: RUSTPYTHON; flaky")
@os_helper.skip_unless_symlink
def test_chmod_dir_symlink(self):
target = self.tempdir()
@@ -1110,7 +1109,7 @@ class PosixTester(unittest.TestCase):
def _test_chflags_regular_file(self, chflags_func, target_file, **kwargs):
st = os.stat(target_file)
self.assertTrue(hasattr(st, 'st_flags'))
self.assertHasAttr(st, 'st_flags')
# ZFS returns EOPNOTSUPP when attempting to set flag UF_IMMUTABLE.
flags = st.st_flags | stat.UF_IMMUTABLE
@@ -1146,7 +1145,7 @@ class PosixTester(unittest.TestCase):
def test_lchflags_symlink(self):
testfn_st = os.stat(os_helper.TESTFN)
self.assertTrue(hasattr(testfn_st, 'st_flags'))
self.assertHasAttr(testfn_st, 'st_flags')
self.addCleanup(os_helper.unlink, _DUMMY_SYMLINK)
os.symlink(os_helper.TESTFN, _DUMMY_SYMLINK)
@@ -1350,7 +1349,6 @@ class PosixTester(unittest.TestCase):
param = posix.sched_param(sched_priority=-large)
self.assertRaises(OverflowError, posix.sched_setparam, 0, param)
@unittest.expectedFailureIf(sys.platform == 'linux', "TODO: RUSTPYTHON; TypeError: cannot pickle 'sched_param' object")
@requires_sched
def test_sched_param(self):
param = posix.sched_param(1)
@@ -1370,6 +1368,14 @@ class PosixTester(unittest.TestCase):
self.assertNotEqual(newparam, param)
self.assertEqual(newparam.sched_priority, 0)
@requires_sched
def test_bug_140634(self):
sched_priority = float('inf') # any new reference
param = posix.sched_param(sched_priority)
param.__reduce__()
del sched_priority, param # should not crash
support.gc_collect() # just to be sure
@unittest.skipUnless(hasattr(posix, "sched_rr_get_interval"), "no function")
def test_sched_rr_get_interval(self):
try:
@@ -1525,6 +1531,51 @@ class PosixTester(unittest.TestCase):
self.assertEqual(cm.exception.errno, errno.EINVAL)
os.close(os.pidfd_open(os.getpid(), 0))
@os_helper.skip_unless_hardlink
@os_helper.skip_unless_symlink
def test_link_follow_symlinks(self):
default_follow = sys.platform.startswith(
('darwin', 'freebsd', 'netbsd', 'openbsd', 'dragonfly', 'sunos5'))
default_no_follow = sys.platform.startswith(('win32', 'linux'))
orig = os_helper.TESTFN
symlink = orig + 'symlink'
posix.symlink(orig, symlink)
self.addCleanup(os_helper.unlink, symlink)
with self.subTest('no follow_symlinks'):
# no follow_symlinks -> platform depending
link = orig + 'link'
posix.link(symlink, link)
self.addCleanup(os_helper.unlink, link)
if os.link in os.supports_follow_symlinks or default_follow:
self.assertEqual(posix.lstat(link), posix.lstat(orig))
elif default_no_follow:
self.assertEqual(posix.lstat(link), posix.lstat(symlink))
with self.subTest('follow_symlinks=False'):
# follow_symlinks=False -> duplicate the symlink itself
link = orig + 'link_nofollow'
try:
posix.link(symlink, link, follow_symlinks=False)
except NotImplementedError:
if os.link in os.supports_follow_symlinks or default_no_follow:
raise
else:
self.addCleanup(os_helper.unlink, link)
self.assertEqual(posix.lstat(link), posix.lstat(symlink))
with self.subTest('follow_symlinks=True'):
# follow_symlinks=True -> duplicate the target file
link = orig + 'link_following'
try:
posix.link(symlink, link, follow_symlinks=True)
except NotImplementedError:
if os.link in os.supports_follow_symlinks or default_follow:
raise
else:
self.addCleanup(os_helper.unlink, link)
self.assertEqual(posix.lstat(link), posix.lstat(orig))
# tests for the posix *at functions follow
class TestPosixDirFd(unittest.TestCase):
@@ -1570,7 +1621,6 @@ class TestPosixDirFd(unittest.TestCase):
with self.prepare_file() as (dir_fd, name, fullname):
posix.chown(name, os.getuid(), os.getgid(), dir_fd=dir_fd)
@unittest.expectedFailureIf(sys.platform in ('darwin', 'linux'), 'TODO: RUSTPYTHON; AssertionError: RuntimeWarning not triggered')
@unittest.skipUnless(os.stat in os.supports_dir_fd, "test needs dir_fd support in os.stat()")
def test_stat_dir_fd(self):
with self.prepare() as (dir_fd, name, fullname):
@@ -1973,7 +2023,7 @@ class _PosixSpawnMixin:
[sys.executable, "-c", "pass"],
os.environ, setsigdef=[signal.NSIG, signal.NSIG+1])
@unittest.expectedFailureIf(sys.platform in ('darwin', 'linux'), 'TODO: RUSTPYTHON; NotImplementedError: scheduler parameter is not yet implemented')
@unittest.expectedFailureIf(sys.platform in ("darwin", "linux"), "TODO: RUSTPYTHON; NotImplementedError: scheduler parameter is not yet implemented")
@requires_sched
@unittest.skipIf(sys.platform.startswith(('freebsd', 'netbsd')),
"bpo-34685: test can fail on BSD")
@@ -1994,14 +2044,15 @@ class _PosixSpawnMixin:
)
support.wait_process(pid, exitcode=0)
@unittest.expectedFailureIf(sys.platform in ('darwin', 'linux'), 'TODO: RUSTPYTHON; NotImplementedError: scheduler parameter is not yet implemented')
@unittest.expectedFailureIf(sys.platform in ("darwin", "linux"), "TODO: RUSTPYTHON; NotImplementedError: scheduler parameter is not yet implemented")
@requires_sched
@unittest.skipIf(sys.platform.startswith(('freebsd', 'netbsd')),
"bpo-34685: test can fail on BSD")
@unittest.skipIf(platform.libc_ver()[0] == 'glibc' and
os.sched_getscheduler(0) in [
os.SCHED_BATCH,
os.SCHED_IDLE],
os.SCHED_IDLE,
os.SCHED_DEADLINE],
"Skip test due to glibc posix_spawn policy")
def test_setscheduler_with_policy(self):
policy = os.sched_getscheduler(0)
@@ -2081,7 +2132,7 @@ class _PosixSpawnMixin:
with open(outfile, encoding="utf-8") as f:
self.assertEqual(f.read(), 'hello')
@unittest.expectedFailure # TODO: RUSTPYTHON; the rust runtime reopens closed stdio fds at startup, so this test fails, even though POSIX_SPAWN_CLOSE does actually have an effect
@unittest.expectedFailure # TODO: RUSTPYTHON; the rust runtime reopens closed stdio fds at startup, so this test fails, even though POSIX_SPAWN_CLOSE does actually have an effect
def test_close_file(self):
closefile = os_helper.TESTFN
self.addCleanup(os_helper.unlink, closefile)
@@ -2186,12 +2237,12 @@ class TestPosixWeaklinking(unittest.TestCase):
def test_pwritev(self):
self._verify_available("HAVE_PWRITEV")
if self.mac_ver >= (10, 16):
self.assertTrue(hasattr(os, "pwritev"), "os.pwritev is not available")
self.assertTrue(hasattr(os, "preadv"), "os.readv is not available")
self.assertHasAttr(os, "pwritev")
self.assertHasAttr(os, "preadv")
else:
self.assertFalse(hasattr(os, "pwritev"), "os.pwritev is available")
self.assertFalse(hasattr(os, "preadv"), "os.readv is available")
self.assertNotHasAttr(os, "pwritev")
self.assertNotHasAttr(os, "preadv")
def test_stat(self):
self._verify_available("HAVE_FSTATAT")
-2
View File
@@ -192,7 +192,6 @@ class WindowsSignalTests(unittest.TestCase):
self.assertNotIn(signal.NSIG, s)
self.assertLess(len(s), signal.NSIG)
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_issue9324(self):
# Updated for issue #10003, adding SIGBREAK
handler = lambda x, y: None
@@ -1414,7 +1413,6 @@ class RaiseSignalTest(unittest.TestCase):
with self.assertRaises(KeyboardInterrupt):
signal.raise_signal(signal.SIGINT)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.skipIf(sys.platform != "win32", "Windows specific test")
def test_invalid_argument(self):
try:
-1
View File
@@ -800,7 +800,6 @@ class ProcessTestCase(BaseTestCase):
stdout, stderr = p.communicate()
self.assertEqual(stdout, b"orange")
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.skipUnless(sys.platform == "win32", "Windows only issue")
def test_win32_duplicate_envs(self):
newenv = os.environ.copy()
+39 -8
View File
@@ -16,8 +16,8 @@ mod _socket {
common::os::ErrorExt,
convert::{IntoPyException, ToPyObject, TryFromBorrowedObject, TryFromObject},
function::{
ArgBytesLike, ArgMemoryBuffer, ArgStrOrBytesLike, Either, FsPath, OptionalArg,
OptionalOption,
ArgBytesLike, ArgIntoFloat, ArgMemoryBuffer, ArgStrOrBytesLike, Either, FsPath,
OptionalArg, OptionalOption,
},
types::{Constructor, DefaultConstructor, Initializer, Representable},
utils::ToCString,
@@ -2201,12 +2201,29 @@ mod _socket {
}
#[pymethod]
fn settimeout(&self, timeout: Option<Duration>) -> io::Result<()> {
self.timeout
.store(timeout.map_or(-1.0, |d| d.as_secs_f64()));
fn settimeout(&self, timeout: Option<ArgIntoFloat>, vm: &VirtualMachine) -> PyResult<()> {
let timeout = match timeout {
Some(t) => {
let f = t.into_float();
if f.is_nan() {
return Err(
vm.new_value_error("Invalid value NaN (not a number)".to_owned())
);
}
if f < 0.0 || !f.is_finite() {
return Err(vm.new_value_error("Timeout value out of range".to_owned()));
}
Some(f)
}
None => None,
};
self.timeout.store(timeout.unwrap_or(-1.0));
// even if timeout is > 0 the socket needs to be nonblocking in order for us to select() on
// it
self.sock()?.set_nonblocking(timeout.is_some())
self.sock()
.map_err(|e| e.into_pyexception(vm))?
.set_nonblocking(timeout.is_some())
.map_err(|e| e.into_pyexception(vm))
}
#[pymethod]
@@ -3366,8 +3383,22 @@ mod _socket {
}
#[pyfunction]
fn setdefaulttimeout(timeout: Option<Duration>) {
DEFAULT_TIMEOUT.store(timeout.map_or(-1.0, |d| d.as_secs_f64()));
fn setdefaulttimeout(timeout: Option<ArgIntoFloat>, vm: &VirtualMachine) -> PyResult<()> {
let val = match timeout {
Some(t) => {
let f = t.into_float();
if f.is_nan() {
return Err(vm.new_value_error("Invalid value NaN (not a number)".to_owned()));
}
if f < 0.0 || !f.is_finite() {
return Err(vm.new_value_error("Timeout value out of range".to_owned()));
}
f
}
None => -1.0,
};
DEFAULT_TIMEOUT.store(val);
Ok(())
}
#[pyfunction]
+16 -3
View File
@@ -126,10 +126,23 @@ impl TryFromObject for core::time::Duration {
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
if let Some(float) = obj.downcast_ref::<PyFloat>() {
let f = float.to_f64();
if f < 0.0 {
return Err(vm.new_value_error("negative duration"));
if f.is_nan() {
return Err(vm.new_value_error("Invalid value NaN (not a number)".to_owned()));
}
Ok(Self::from_secs_f64(f))
if f < 0.0 {
return Err(vm.new_value_error("negative duration".to_owned()));
}
if !f.is_finite() || f > u64::MAX as f64 {
return Err(vm.new_overflow_error(
"timestamp too large to convert to C PyTime_t".to_owned(),
));
}
// Convert float to Duration using floor rounding (_PyTime_ROUND_FLOOR)
let secs = f.trunc() as u64;
let frac = f.fract();
// Use floor to round down the nanoseconds
let nanos = (frac * 1_000_000_000.0).floor() as u32;
Ok(Self::new(secs, nanos))
} else if let Some(int) = obj.try_index_opt(vm) {
let int = int?;
let bigint = int.as_bigint();
+14 -1
View File
@@ -1,8 +1,9 @@
use rustpython_common::crt_fd;
use crate::{
PyObjectRef, PyResult, VirtualMachine,
AsObject, PyObjectRef, PyResult, VirtualMachine,
builtins::{PyBytes, PyStr},
class::StaticType,
convert::{IntoPyException, ToPyException, ToPyObject, TryFromObject},
function::FsPath,
};
@@ -80,6 +81,18 @@ impl PathConverter {
) -> PyResult<OsPathOrFd<'fd>> {
// Handle fd (before __fspath__ check, like CPython)
if let Some(int) = obj.try_index_opt(vm) {
// Warn if bool is used as a file descriptor
if obj
.class()
.is(crate::builtins::bool_::PyBool::static_type())
{
crate::stdlib::warnings::warn(
vm.ctx.exceptions.runtime_warning,
"bool is used as a file descriptor".to_owned(),
1,
vm,
)?;
}
let fd = int?.try_to_primitive(vm)?;
return unsafe { crt_fd::Borrowed::try_borrow_raw(fd) }
.map(OsPathOrFd::Fd)
+26 -19
View File
@@ -183,6 +183,18 @@ pub(crate) mod module {
environ
}
#[pyfunction]
fn _create_environ(vm: &VirtualMachine) -> PyDictRef {
let environ = vm.ctx.new_dict();
for (key, value) in env::vars() {
if key.starts_with('=') {
continue;
}
environ.set_item(&key, vm.new_pyobj(value), vm).unwrap();
}
environ
}
#[derive(FromArgs)]
struct ChmodArgs<'a> {
#[pyarg(any)]
@@ -903,16 +915,14 @@ pub(crate) mod module {
argv: Either<PyListRef, PyTupleRef>,
vm: &VirtualMachine,
) -> PyResult<intptr_t> {
use crate::function::FsPath;
use std::iter::once;
let make_widestring =
|s: &str| widestring::WideCString::from_os_str(s).map_err(|err| err.to_pyexception(vm));
let path = path.to_wide_cstring(vm)?;
let argv = vm.extract_elements_with(argv.as_ref(), |obj| {
let arg = PyStrRef::try_from_object(vm, obj)?;
make_widestring(arg.as_str())
let fspath = FsPath::try_from_path_like(obj, true, vm)?;
fspath.to_wide_cstring(vm)
})?;
let first = argv
@@ -946,16 +956,14 @@ pub(crate) mod module {
env: PyDictRef,
vm: &VirtualMachine,
) -> PyResult<intptr_t> {
use crate::function::FsPath;
use std::iter::once;
let make_widestring =
|s: &str| widestring::WideCString::from_os_str(s).map_err(|err| err.to_pyexception(vm));
let path = path.to_wide_cstring(vm)?;
let argv = vm.extract_elements_with(argv.as_ref(), |obj| {
let arg = PyStrRef::try_from_object(vm, obj)?;
make_widestring(arg.as_str())
let fspath = FsPath::try_from_path_like(obj, true, vm)?;
fspath.to_wide_cstring(vm)
})?;
let first = argv
@@ -975,15 +983,11 @@ pub(crate) mod module {
// Build environment strings as "KEY=VALUE\0" wide strings
let mut env_strings: Vec<widestring::WideCString> = Vec::new();
for (key, value) in env.into_iter() {
let key = PyStrRef::try_from_object(vm, key)?;
let value = PyStrRef::try_from_object(vm, value)?;
let key_str = key.as_str();
let value_str = value.as_str();
let key = FsPath::try_from_path_like(key, true, vm)?;
let value = FsPath::try_from_path_like(value, true, vm)?;
let key_str = key.to_string_lossy();
let value_str = value.to_string_lossy();
// Validate: no null characters in key or value
if key_str.contains('\0') || value_str.contains('\0') {
return Err(vm.new_value_error("embedded null character"));
}
// Validate: no '=' in key (search from index 1 because on Windows
// starting '=' is allowed for defining hidden environment variables)
if key_str.get(1..).is_some_and(|s| s.contains('=')) {
@@ -991,7 +995,10 @@ pub(crate) mod module {
}
let env_str = format!("{}={}", key_str, value_str);
env_strings.push(make_widestring(&env_str)?);
env_strings.push(
widestring::WideCString::from_os_str(&*std::ffi::OsString::from(env_str))
.map_err(|err| err.to_pyexception(vm))?,
);
}
let envp: Vec<*const u16> = env_strings
+90 -23
View File
@@ -87,6 +87,7 @@ impl<const AVAILABLE: usize> FromArgs for DirFd<'_, AVAILABLE> {
Some(o) if vm.is_none(&o) => Ok(DEFAULT_DIR_FD),
None => Ok(DEFAULT_DIR_FD),
Some(o) => {
warn_if_bool_fd(&o, vm).map_err(Into::<ArgumentError>::into)?;
let fd = o.try_index_opt(vm).unwrap_or_else(|| {
Err(vm.new_type_error(format!(
"argument should be integer or None, not {}",
@@ -118,8 +119,25 @@ fn bytes_as_os_str<'a>(b: &'a [u8], vm: &VirtualMachine) -> PyResult<&'a std::ff
.map_err(|_| vm.new_unicode_decode_error("can't decode path for utf-8"))
}
pub(crate) fn warn_if_bool_fd(obj: &PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
use crate::class::StaticType;
if obj
.class()
.is(crate::builtins::bool_::PyBool::static_type())
{
crate::stdlib::warnings::warn(
vm.ctx.exceptions.runtime_warning,
"bool is used as a file descriptor".to_owned(),
1,
vm,
)?;
}
Ok(())
}
impl TryFromObject for crt_fd::Owned {
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
warn_if_bool_fd(&obj, vm)?;
let fd = crt_fd::Raw::try_from_object(vm, obj)?;
unsafe { crt_fd::Owned::try_from_raw(fd) }.map_err(|e| e.into_pyexception(vm))
}
@@ -127,6 +145,7 @@ impl TryFromObject for crt_fd::Owned {
impl TryFromObject for crt_fd::Borrowed<'_> {
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
warn_if_bool_fd(&obj, vm)?;
let fd = crt_fd::Raw::try_from_object(vm, obj)?;
unsafe { crt_fd::Borrowed::try_borrow_raw(fd) }.map_err(|e| e.into_pyexception(vm))
}
@@ -165,11 +184,11 @@ pub(super) mod _os {
},
convert::{IntoPyException, ToPyObject},
exceptions::OSErrorBuilder,
function::{ArgBytesLike, FsPath, FuncArgs, OptionalArg},
function::{ArgBytesLike, ArgMemoryBuffer, FsPath, FuncArgs, OptionalArg},
ospath::{OsPath, OsPathOrFd, OutputMode, PathConverter},
protocol::PyIterReturn,
recursion::ReprGuard,
types::{IterNext, Iterable, PyStructSequence, Representable, SelfIter},
types::{Destructor, IterNext, Iterable, PyStructSequence, Representable, SelfIter},
vm::VirtualMachine,
};
use core::time::Duration;
@@ -296,6 +315,26 @@ pub(super) mod _os {
}
}
#[pyfunction]
fn readinto(
fd: crt_fd::Borrowed<'_>,
buffer: ArgMemoryBuffer,
vm: &VirtualMachine,
) -> PyResult<usize> {
buffer.with_ref(|buf| {
loop {
match crt_fd::read(fd, buf) {
Ok(n) => return Ok(n),
Err(e) if e.raw_os_error() == Some(libc::EINTR) => {
vm.check_signals()?;
continue;
}
Err(e) => return Err(e.into_pyexception(vm)),
}
}
})
}
#[pyfunction]
fn write(fd: crt_fd::Borrowed<'_>, data: ArgBytesLike) -> io::Result<usize> {
data.with_ref(|b| crt_fd::write(fd, b))
@@ -754,7 +793,7 @@ pub(super) mod _os {
mode: OutputMode,
}
#[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))]
#[pyclass(flags(DISALLOW_INSTANTIATION), with(Destructor, IterNext, Iterable))]
impl ScandirIterator {
#[pymethod]
fn close(&self) {
@@ -777,6 +816,21 @@ pub(super) mod _os {
Err(vm.new_type_error("cannot pickle 'ScandirIterator' object".to_owned()))
}
}
impl Destructor for ScandirIterator {
fn del(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<()> {
// Emit ResourceWarning if the iterator is not yet exhausted/closed
if zelf.entries.read().is_some() {
let _ = crate::stdlib::warnings::warn(
vm.ctx.exceptions.resource_warning,
format!("unclosed scandir iterator {:?}", zelf.as_object()),
1,
vm,
);
zelf.close();
}
Ok(())
}
}
impl SelfIter for ScandirIterator {}
impl IterNext for ScandirIterator {
fn next(zelf: &crate::Py<Self>, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
@@ -1267,13 +1321,24 @@ pub(super) mod _os {
}
}
#[pyfunction]
fn link(
#[derive(FromArgs)]
struct LinkArgs {
#[pyarg(any)]
src: OsPath,
#[pyarg(any)]
dst: OsPath,
follow_symlinks: FollowSymlinks,
vm: &VirtualMachine,
) -> PyResult<()> {
#[pyarg(named, name = "follow_symlinks", optional)]
follow_symlinks: OptionalArg<bool>,
}
#[pyfunction]
fn link(args: LinkArgs, vm: &VirtualMachine) -> PyResult<()> {
let LinkArgs {
src,
dst,
follow_symlinks,
} = args;
#[cfg(unix)]
{
use std::os::unix::ffi::OsStrExt;
@@ -1282,11 +1347,8 @@ pub(super) mod _os {
let dst_cstr = std::ffi::CString::new(dst.path.as_os_str().as_bytes())
.map_err(|_| vm.new_value_error("embedded null byte"))?;
let flags = if follow_symlinks.0 {
libc::AT_SYMLINK_FOLLOW
} else {
0
};
let follow = follow_symlinks.into_option().unwrap_or(true);
let flags = if follow { libc::AT_SYMLINK_FOLLOW } else { 0 };
let ret = unsafe {
libc::linkat(
@@ -1311,15 +1373,18 @@ pub(super) mod _os {
#[cfg(not(unix))]
{
// On non-Unix platforms, ignore follow_symlinks if it's the default value
// or raise NotImplementedError if explicitly set to False
if !follow_symlinks.0 {
return Err(vm.new_not_implemented_error(
"link: follow_symlinks unavailable on this platform",
));
}
let src_path = match follow_symlinks.into_option() {
Some(true) => {
// Explicit follow_symlinks=True: resolve symlinks
fs::canonicalize(&src.path).unwrap_or_else(|_| PathBuf::from(src.path.clone()))
}
Some(false) | None => {
// Default or explicit no-follow: native hard_link behavior
PathBuf::from(src.path.clone())
}
};
fs::hard_link(&src.path, &dst.path).map_err(|err| {
fs::hard_link(&src_path, &dst.path).map_err(|err| {
let builder = err.to_os_error_builder(vm);
let builder = builder.filename(src.filename(vm));
let builder = builder.filename2(dst.filename(vm));
@@ -1657,8 +1722,10 @@ pub(super) mod _os {
#[pyfunction]
fn truncate(path: PyObjectRef, length: crt_fd::Offset, vm: &VirtualMachine) -> PyResult<()> {
if let Ok(fd) = path.clone().try_into_value(vm) {
return ftruncate(fd, length).map_err(|e| e.into_pyexception(vm));
match path.clone().try_into_value::<crt_fd::Borrowed<'_>>(vm) {
Ok(fd) => return ftruncate(fd, length).map_err(|e| e.into_pyexception(vm)),
Err(e) if e.fast_isinstance(vm.ctx.exceptions.warning) => return Err(e),
Err(_) => {}
}
#[cold]
+215 -182
View File
@@ -15,19 +15,36 @@ pub fn set_inheritable(fd: BorrowedFd<'_>, inheritable: bool) -> nix::Result<()>
Ok(())
}
#[pymodule(name = "posix", with(super::os::_os))]
#[pymodule(name = "posix", with(
super::os::_os,
#[cfg(any(
target_os = "linux",
target_os = "netbsd",
target_os = "freebsd",
target_os = "android"
))]
posix_sched
))]
pub mod module {
use crate::{
AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine,
builtins::{PyDictRef, PyInt, PyListRef, PyStrRef, PyTupleRef, PyType, PyUtf8StrRef},
AsObject, Py, PyObjectRef, PyResult, VirtualMachine,
builtins::{PyDictRef, PyInt, PyListRef, PyStr, PyTupleRef},
convert::{IntoPyException, ToPyObject, TryFromObject},
exceptions::OSErrorBuilder,
function::{Either, KwArgs, OptionalArg},
ospath::{OsPath, OsPathOrFd},
stdlib::os::{_os, DirFd, FollowSymlinks, SupportFunc, TargetIsDirectory, fs_metadata},
types::{Constructor, Representable},
utils::ToCString,
stdlib::os::{
_os, DirFd, FollowSymlinks, SupportFunc, TargetIsDirectory, fs_metadata,
warn_if_bool_fd,
},
};
#[cfg(any(
target_os = "android",
target_os = "freebsd",
target_os = "linux",
target_os = "openbsd"
))]
use crate::{builtins::PyStrRef, utils::ToCString};
use alloc::ffi::CString;
use bitflags::bitflags;
use core::ffi::CStr;
@@ -41,7 +58,7 @@ pub mod module {
};
use strum_macros::{EnumIter, EnumString};
#[cfg(target_os = "android")]
#[cfg(any(target_os = "android", target_os = "linux"))]
#[pyattr]
use libc::{SCHED_DEADLINE, SCHED_NORMAL};
@@ -278,6 +295,7 @@ pub mod module {
impl TryFromObject for BorrowedFd<'_> {
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
crate::stdlib::os::warn_if_bool_fd(&obj, vm)?;
let fd = i32::try_from_object(vm, obj)?;
if fd == -1 {
return Err(io::Error::from_raw_os_error(libc::EBADF).into_pyexception(vm));
@@ -443,6 +461,19 @@ pub mod module {
environ
}
#[pyfunction]
fn _create_environ(vm: &VirtualMachine) -> PyDictRef {
use rustpython_common::os::ffi::OsStringExt;
let environ = vm.ctx.new_dict();
for (key, value) in env::vars_os() {
let key: PyObjectRef = vm.ctx.new_bytes(key.into_vec()).into();
let value: PyObjectRef = vm.ctx.new_bytes(value.into_vec()).into();
environ.set_item(&*key, value, vm).unwrap();
}
environ
}
#[derive(FromArgs)]
pub(super) struct SymlinkArgs<'fd> {
src: OsPath,
@@ -483,8 +514,15 @@ pub mod module {
#[cfg(not(target_os = "redox"))]
#[pyfunction]
fn fchdir(fd: BorrowedFd<'_>, vm: &VirtualMachine) -> PyResult<()> {
nix::unistd::fchdir(fd).map_err(|err| err.into_pyexception(vm))
fn fchdir(fd: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
warn_if_bool_fd(&fd, vm)?;
let fd = i32::try_from_object(vm, fd)?;
let ret = unsafe { libc::fchdir(fd) };
if ret == 0 {
Ok(())
} else {
Err(io::Error::last_os_error().into_pyexception(vm))
}
}
#[cfg(not(target_os = "redox"))]
@@ -859,175 +897,6 @@ pub mod module {
nix::sched::sched_yield().map_err(|e| e.into_pyexception(vm))
}
#[pyattr]
#[pyclass(name = "sched_param")]
#[derive(Debug, PyPayload)]
struct SchedParam {
sched_priority: PyObjectRef,
}
impl TryFromObject for SchedParam {
fn try_from_object(_vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
Ok(Self {
sched_priority: obj,
})
}
}
#[pyclass(with(Constructor, Representable))]
impl SchedParam {
#[pygetset]
fn sched_priority(&self, vm: &VirtualMachine) -> PyObjectRef {
self.sched_priority.clone().to_pyobject(vm)
}
#[cfg(any(
target_os = "linux",
target_os = "netbsd",
target_os = "freebsd",
target_os = "android"
))]
#[cfg(not(target_env = "musl"))]
fn try_to_libc(&self, vm: &VirtualMachine) -> PyResult<libc::sched_param> {
use crate::AsObject;
let priority_class = self.sched_priority.class();
let priority_type = priority_class.name();
let priority = self.sched_priority.clone();
let value = priority.downcast::<PyInt>().map_err(|_| {
vm.new_type_error(format!("an integer is required (got type {priority_type})"))
})?;
let sched_priority = value.try_to_primitive(vm)?;
Ok(libc::sched_param { sched_priority })
}
}
#[derive(FromArgs)]
pub struct SchedParamArg {
sched_priority: PyObjectRef,
}
impl Constructor for SchedParam {
type Args = SchedParamArg;
fn py_new(_cls: &Py<PyType>, arg: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
Ok(Self {
sched_priority: arg.sched_priority,
})
}
}
impl Representable for SchedParam {
#[inline]
fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
let sched_priority_repr = zelf.sched_priority.repr(vm)?;
Ok(format!(
"posix.sched_param(sched_priority = {})",
sched_priority_repr.as_str()
))
}
}
#[cfg(any(
target_os = "linux",
target_os = "netbsd",
target_os = "freebsd",
target_os = "android"
))]
#[pyfunction]
fn sched_getscheduler(pid: libc::pid_t, vm: &VirtualMachine) -> PyResult<i32> {
let policy = unsafe { libc::sched_getscheduler(pid) };
if policy == -1 {
Err(vm.new_last_errno_error())
} else {
Ok(policy)
}
}
#[cfg(any(
target_os = "linux",
target_os = "netbsd",
target_os = "freebsd",
target_os = "android"
))]
#[derive(FromArgs)]
struct SchedSetschedulerArgs {
#[pyarg(positional)]
pid: i32,
#[pyarg(positional)]
policy: i32,
#[pyarg(positional)]
sched_param_obj: crate::PyRef<SchedParam>,
}
#[cfg(any(
target_os = "linux",
target_os = "netbsd",
target_os = "freebsd",
target_os = "android"
))]
#[cfg(not(target_env = "musl"))]
#[pyfunction]
fn sched_setscheduler(args: SchedSetschedulerArgs, vm: &VirtualMachine) -> PyResult<i32> {
let libc_sched_param = args.sched_param_obj.try_to_libc(vm)?;
let policy = unsafe { libc::sched_setscheduler(args.pid, args.policy, &libc_sched_param) };
if policy == -1 {
Err(vm.new_last_errno_error())
} else {
Ok(policy)
}
}
#[cfg(any(
target_os = "linux",
target_os = "netbsd",
target_os = "freebsd",
target_os = "android"
))]
#[pyfunction]
fn sched_getparam(pid: libc::pid_t, vm: &VirtualMachine) -> PyResult<SchedParam> {
let param = unsafe {
let mut param = core::mem::MaybeUninit::uninit();
if -1 == libc::sched_getparam(pid, param.as_mut_ptr()) {
return Err(vm.new_last_errno_error());
}
param.assume_init()
};
Ok(SchedParam {
sched_priority: param.sched_priority.to_pyobject(vm),
})
}
#[cfg(any(
target_os = "linux",
target_os = "netbsd",
target_os = "freebsd",
target_os = "android"
))]
#[derive(FromArgs)]
struct SchedSetParamArgs {
#[pyarg(positional)]
pid: i32,
#[pyarg(positional)]
sched_param_obj: crate::PyRef<SchedParam>,
}
#[cfg(any(
target_os = "linux",
target_os = "netbsd",
target_os = "freebsd",
target_os = "android"
))]
#[cfg(not(target_env = "musl"))]
#[pyfunction]
fn sched_setparam(args: SchedSetParamArgs, vm: &VirtualMachine) -> PyResult<i32> {
let libc_sched_param = args.sched_param_obj.try_to_libc(vm)?;
let ret = unsafe { libc::sched_setparam(args.pid, &libc_sched_param) };
if ret == -1 {
Err(vm.new_last_errno_error())
} else {
Ok(ret)
}
}
#[pyfunction]
fn get_inheritable(fd: BorrowedFd<'_>, vm: &VirtualMachine) -> PyResult<bool> {
let flags = fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFD);
@@ -1183,7 +1052,7 @@ pub mod module {
let path = path.into_cstring(vm)?;
let argv = vm.extract_elements_with(argv.as_ref(), |obj| {
PyStrRef::try_from_object(vm, obj)?.to_cstring(vm)
OsPath::try_from_object(vm, obj)?.into_cstring(vm)
})?;
let argv: Vec<&CStr> = argv.iter().map(|entry| entry.as_c_str()).collect();
@@ -1209,7 +1078,7 @@ pub mod module {
let path = path.into_cstring(vm)?;
let argv = vm.extract_elements_with(argv.as_ref(), |obj| {
PyStrRef::try_from_object(vm, obj)?.to_cstring(vm)
OsPath::try_from_object(vm, obj)?.into_cstring(vm)
})?;
let argv: Vec<&CStr> = argv.iter().map(|entry| entry.as_c_str()).collect();
@@ -2068,7 +1937,11 @@ pub mod module {
let i = match obj.downcast::<PyInt>() {
Ok(int) => int.try_to_primitive(vm)?,
Err(obj) => {
let s = PyStrRef::try_from_object(vm, obj)?;
let s = obj.downcast::<PyStr>().map_err(|_| {
vm.new_type_error(
"configuration names must be strings or integers".to_owned(),
)
})?;
s.as_str()
.parse::<PathconfVar>()
.map_err(|_| vm.new_value_error("unrecognized configuration name"))?
@@ -2462,7 +2335,11 @@ pub mod module {
let i = match obj.downcast::<PyInt>() {
Ok(int) => int.try_to_primitive(vm)?,
Err(obj) => {
let s = PyUtf8StrRef::try_from_object(vm, obj)?;
let s = obj.downcast::<PyStr>().map_err(|_| {
vm.new_type_error(
"configuration names must be strings or integers".to_owned(),
)
})?;
s.as_str().parse::<SysconfVar>().or_else(|_| {
if s.as_str() == "SC_PAGESIZE" {
Ok(SysconfVar::SC_PAGESIZE)
@@ -2630,3 +2507,159 @@ pub mod module {
Ok(())
}
}
#[cfg(any(
target_os = "linux",
target_os = "netbsd",
target_os = "freebsd",
target_os = "android"
))]
#[pymodule(sub)]
mod posix_sched {
use crate::{
AsObject, Py, PyObjectRef, PyResult, VirtualMachine,
builtins::{PyInt, PyTupleRef},
convert::ToPyObject,
function::FuncArgs,
types::PyStructSequence,
};
#[derive(FromArgs)]
struct SchedParamArgs {
#[pyarg(any)]
sched_priority: PyObjectRef,
}
#[pystruct_sequence_data]
struct SchedParamData {
pub sched_priority: PyObjectRef,
}
#[pyattr]
#[pystruct_sequence(name = "sched_param", module = "posix", data = "SchedParamData")]
struct PySchedParam;
#[pyclass(with(PyStructSequence))]
impl PySchedParam {
#[pyslot]
fn slot_new(
cls: crate::builtins::PyTypeRef,
args: FuncArgs,
vm: &VirtualMachine,
) -> PyResult {
use crate::PyPayload;
let SchedParamArgs { sched_priority } = args.bind(vm)?;
let items = vec![sched_priority];
crate::builtins::PyTuple::new_unchecked(items.into_boxed_slice())
.into_ref_with_type(vm, cls)
.map(Into::into)
}
#[extend_class]
fn extend_pyclass(ctx: &crate::vm::Context, class: &'static Py<crate::builtins::PyType>) {
// Override __reduce__ to return (type, (sched_priority,))
// instead of the generic structseq (type, ((sched_priority,),)).
// The trait's extend_class checks contains_key before setting default.
const SCHED_PARAM_REDUCE: crate::function::PyMethodDef =
crate::function::PyMethodDef::new_const(
"__reduce__",
|zelf: crate::PyRef<crate::builtins::PyTuple>,
vm: &VirtualMachine|
-> PyTupleRef {
vm.new_tuple((zelf.class().to_owned(), (zelf[0].clone(),)))
},
crate::function::PyMethodFlags::METHOD,
None,
);
class.set_attr(
ctx.intern_str("__reduce__"),
SCHED_PARAM_REDUCE.to_proper_method(class, ctx),
);
}
}
#[cfg(not(target_env = "musl"))]
fn convert_sched_param(obj: &PyObjectRef, vm: &VirtualMachine) -> PyResult<libc::sched_param> {
use crate::{builtins::PyTuple, class::StaticType};
if !obj.fast_isinstance(PySchedParam::static_type()) {
return Err(vm.new_type_error("must have a sched_param object".to_owned()));
}
let tuple = obj.downcast_ref::<PyTuple>().unwrap();
let priority = tuple[0].clone();
let priority_type = priority.class().name().to_string();
let value = priority.downcast::<PyInt>().map_err(|_| {
vm.new_type_error(format!("an integer is required (got type {priority_type})"))
})?;
let sched_priority = value.try_to_primitive(vm)?;
Ok(libc::sched_param { sched_priority })
}
#[pyfunction]
fn sched_getscheduler(pid: libc::pid_t, vm: &VirtualMachine) -> PyResult<i32> {
let policy = unsafe { libc::sched_getscheduler(pid) };
if policy == -1 {
Err(vm.new_last_errno_error())
} else {
Ok(policy)
}
}
#[derive(FromArgs)]
struct SchedSetschedulerArgs {
#[pyarg(positional)]
pid: i32,
#[pyarg(positional)]
policy: i32,
#[pyarg(positional)]
sched_param: PyObjectRef,
}
#[cfg(not(target_env = "musl"))]
#[pyfunction]
fn sched_setscheduler(args: SchedSetschedulerArgs, vm: &VirtualMachine) -> PyResult<i32> {
let libc_sched_param = convert_sched_param(&args.sched_param, vm)?;
let policy = unsafe { libc::sched_setscheduler(args.pid, args.policy, &libc_sched_param) };
if policy == -1 {
Err(vm.new_last_errno_error())
} else {
Ok(policy)
}
}
#[pyfunction]
fn sched_getparam(pid: libc::pid_t, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
let param = unsafe {
let mut param = core::mem::MaybeUninit::uninit();
if -1 == libc::sched_getparam(pid, param.as_mut_ptr()) {
return Err(vm.new_last_errno_error());
}
param.assume_init()
};
Ok(PySchedParam::from_data(
SchedParamData {
sched_priority: param.sched_priority.to_pyobject(vm),
},
vm,
))
}
#[derive(FromArgs)]
struct SchedSetParamArgs {
#[pyarg(positional)]
pid: i32,
#[pyarg(positional)]
sched_param: PyObjectRef,
}
#[cfg(not(target_env = "musl"))]
#[pyfunction]
fn sched_setparam(args: SchedSetParamArgs, vm: &VirtualMachine) -> PyResult<i32> {
let libc_sched_param = convert_sched_param(&args.sched_param, vm)?;
let ret = unsafe { libc::sched_setparam(args.pid, &libc_sched_param) };
if ret == -1 {
Err(vm.new_last_errno_error())
} else {
Ok(ret)
}
}
}
+26 -3
View File
@@ -101,6 +101,10 @@ pub(crate) mod _signal {
#[pyattr]
pub use libc::{SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM};
#[cfg(windows)]
#[pyattr]
const SIGBREAK: i32 = 21; // _SIGBREAK
// Windows-specific control events for GenerateConsoleCtrlEvent
#[cfg(windows)]
#[pyattr]
@@ -201,6 +205,21 @@ pub(crate) mod _signal {
vm: &VirtualMachine,
) -> PyResult<Option<PyObjectRef>> {
signal::assert_in_range(signalnum, vm)?;
#[cfg(windows)]
{
const VALID_SIGNALS: &[i32] = &[
libc::SIGINT,
libc::SIGILL,
libc::SIGFPE,
libc::SIGSEGV,
libc::SIGTERM,
SIGBREAK,
libc::SIGABRT,
];
if !VALID_SIGNALS.contains(&signalnum) {
return Err(vm.new_value_error(format!("signal number {} out of range", signalnum)));
}
}
let signal_handlers = vm
.signal_handlers
.as_deref()
@@ -482,18 +501,20 @@ pub(crate) mod _signal {
// On Windows, only certain signals are supported
#[cfg(windows)]
{
use crate::convert::IntoPyException;
// Windows supports: SIGINT(2), SIGILL(4), SIGFPE(8), SIGSEGV(11), SIGTERM(15), SIGABRT(22)
// Windows supports: SIGINT(2), SIGILL(4), SIGFPE(8), SIGSEGV(11), SIGTERM(15), SIGBREAK(21), SIGABRT(22)
const VALID_SIGNALS: &[i32] = &[
libc::SIGINT,
libc::SIGILL,
libc::SIGFPE,
libc::SIGSEGV,
libc::SIGTERM,
SIGBREAK,
libc::SIGABRT,
];
if !VALID_SIGNALS.contains(&signalnum) {
return Err(std::io::Error::from_raw_os_error(libc::EINVAL).into_pyexception(vm));
return Err(vm
.new_errno_error(libc::EINVAL, "Invalid argument")
.upcast());
}
}
@@ -537,6 +558,7 @@ pub(crate) mod _signal {
libc::SIGFPE => "Floating-point exception",
libc::SIGSEGV => "Segmentation fault",
libc::SIGTERM => "Terminated",
SIGBREAK => "Break",
libc::SIGABRT => "Aborted",
_ => return Ok(None),
};
@@ -573,6 +595,7 @@ pub(crate) mod _signal {
libc::SIGFPE,
libc::SIGSEGV,
libc::SIGTERM,
SIGBREAK,
libc::SIGABRT,
] {
set.add(vm.ctx.new_int(signum).into(), vm)?;
+19 -5
View File
@@ -437,7 +437,9 @@ mod _winapi {
return Err(vm.new_runtime_error("environment changed size during iteration"));
}
let mut out = widestring::WideString::new();
// Deduplicate case-insensitive keys, keeping the last value
use std::collections::HashMap;
let mut last_entry: HashMap<String, widestring::WideString> = HashMap::new();
for (k, v) in keys.into_iter().zip(values.into_iter()) {
let k = PyStrRef::try_from_object(vm, k)?;
let k = k.as_str();
@@ -449,10 +451,22 @@ mod _winapi {
if k.is_empty() || k[1..].contains('=') {
return Err(vm.new_value_error("illegal environment variable name"));
}
out.push_str(k);
out.push_str("=");
out.push_str(v);
out.push_str("\0");
let key_upper = k.to_uppercase();
let mut entry = widestring::WideString::new();
entry.push_str(k);
entry.push_str("=");
entry.push_str(v);
entry.push_str("\0");
last_entry.insert(key_upper, entry);
}
// Sort by uppercase key for case-insensitive ordering
let mut entries: Vec<(String, widestring::WideString)> = last_entry.into_iter().collect();
entries.sort_by(|a, b| a.0.cmp(&b.0));
let mut out = widestring::WideString::new();
for (_, entry) in entries {
out.push(entry);
}
out.push_str("\0");
Ok(out.into_vec())