Skip to content

gh-132742: Add more tests for fcntl.ioctl() #132756

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 21, 2025
Merged
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
85 changes: 78 additions & 7 deletions Lib/test/test_ioctl.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
else:
with tty:
# Skip if another process is in foreground
r = fcntl.ioctl(tty, termios.TIOCGPGRP, " ")
r = fcntl.ioctl(tty, termios.TIOCGPGRP, struct.pack("i", 0))
rpgrp = struct.unpack("i", r)[0]
if rpgrp not in (os.getpgrp(), os.getsid(0)):
raise unittest.SkipTest("Neither the process group nor the session "
Expand All @@ -27,19 +27,54 @@
pty = None

class IoctlTests(unittest.TestCase):
def test_ioctl(self):
def test_ioctl_immutable_buf(self):
# If this process has been put into the background, TIOCGPGRP returns
# the session ID instead of the process group id.
ids = (os.getpgrp(), os.getsid(0))
with open("/dev/tty", "rb") as tty:
r = fcntl.ioctl(tty, termios.TIOCGPGRP, " ")
rpgrp = struct.unpack("i", r)[0]
# string
buf = " "*8
r = fcntl.ioctl(tty, termios.TIOCGPGRP, buf)
self.assertIsInstance(r, bytes)
rpgrp = memoryview(r).cast('i')[0]
self.assertIn(rpgrp, ids)

def _check_ioctl_mutate_len(self, nbytes=None):
# bytes
buf = b" "*8
r = fcntl.ioctl(tty, termios.TIOCGPGRP, buf)
self.assertIsInstance(r, bytes)
rpgrp = memoryview(r).cast('i')[0]
self.assertIn(rpgrp, ids)

# read-only buffer
r = fcntl.ioctl(tty, termios.TIOCGPGRP, memoryview(buf))
self.assertIsInstance(r, bytes)
rpgrp = memoryview(r).cast('i')[0]
self.assertIn(rpgrp, ids)

def test_ioctl_mutable_buf(self):
ids = (os.getpgrp(), os.getsid(0))
with open("/dev/tty", "rb") as tty:
buf = bytearray(b" "*8)
r = fcntl.ioctl(tty, termios.TIOCGPGRP, buf)
self.assertEqual(r, 0)
rpgrp = memoryview(buf).cast('i')[0]
self.assertIn(rpgrp, ids)

def test_ioctl_no_mutate_buf(self):
ids = (os.getpgrp(), os.getsid(0))
with open("/dev/tty", "rb") as tty:
buf = bytearray(b" "*8)
save_buf = bytes(buf)
r = fcntl.ioctl(tty, termios.TIOCGPGRP, buf, False)
self.assertEqual(bytes(buf), save_buf)
self.assertIsInstance(r, bytes)
rpgrp = memoryview(r).cast('i')[0]
self.assertIn(rpgrp, ids)

def _create_int_buf(self, nbytes=None):
buf = array.array('i')
intsize = buf.itemsize
ids = (os.getpgrp(), os.getsid(0))
# A fill value unlikely to be in `ids`
fill = -12345
if nbytes is not None:
Expand All @@ -48,23 +83,59 @@ def _check_ioctl_mutate_len(self, nbytes=None):
self.assertEqual(len(buf) * intsize, nbytes) # sanity check
else:
buf.append(fill)
return buf

def _check_ioctl_mutate_len(self, nbytes=None):
ids = (os.getpgrp(), os.getsid(0))
buf = self._create_int_buf(nbytes)
with open("/dev/tty", "rb") as tty:
r = fcntl.ioctl(tty, termios.TIOCGPGRP, buf, True)
r = fcntl.ioctl(tty, termios.TIOCGPGRP, buf)
rpgrp = buf[0]
self.assertEqual(r, 0)
self.assertIn(rpgrp, ids)

def _check_ioctl_not_mutate_len(self, nbytes=None):
ids = (os.getpgrp(), os.getsid(0))
buf = self._create_int_buf(nbytes)
save_buf = bytes(buf)
with open("/dev/tty", "rb") as tty:
r = fcntl.ioctl(tty, termios.TIOCGPGRP, buf, False)
self.assertIsInstance(r, bytes)
self.assertEqual(len(r), len(save_buf))
self.assertEqual(bytes(buf), save_buf)
rpgrp = array.array('i', r)[0]
rpgrp = memoryview(r).cast('i')[0]
self.assertIn(rpgrp, ids)

buf = bytes(buf)
with open("/dev/tty", "rb") as tty:
r = fcntl.ioctl(tty, termios.TIOCGPGRP, buf, True)
self.assertIsInstance(r, bytes)
self.assertEqual(len(r), len(save_buf))
self.assertEqual(buf, save_buf)
rpgrp = array.array('i', r)[0]
rpgrp = memoryview(r).cast('i')[0]
self.assertIn(rpgrp, ids)

def test_ioctl_mutate(self):
self._check_ioctl_mutate_len()
self._check_ioctl_not_mutate_len()

def test_ioctl_mutate_1024(self):
# Issue #9758: a mutable buffer of exactly 1024 bytes wouldn't be
# copied back after the system call.
self._check_ioctl_mutate_len(1024)
self._check_ioctl_not_mutate_len(1024)

def test_ioctl_mutate_2048(self):
# Test with a larger buffer, just for the record.
self._check_ioctl_mutate_len(2048)
self.assertRaises(ValueError, self._check_ioctl_not_mutate_len, 2048)

def test_ioctl_tcflush(self):
with open("/dev/tty", "rb") as tty:
r = fcntl.ioctl(tty, termios.TCFLSH, termios.TCIFLUSH)
self.assertEqual(r, 0)

@unittest.skipIf(pty is None, 'pty module required')
def test_ioctl_set_window_size(self):
Expand Down
Loading