Skip to content

Commit 71ceaec

Browse files
gh-95380: Remove the 1024 bytes limit in fcntl.fcntl() and fcntl.ioctl()
1 parent 9f5994b commit 71ceaec

File tree

5 files changed

+137
-38
lines changed

5 files changed

+137
-38
lines changed

Doc/library/fcntl.rst

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,15 +103,16 @@ The module defines the following functions:
103103
passed to the C :c:func:`fcntl` call. The return value after a successful
104104
call is the contents of the buffer, converted to a :class:`bytes` object.
105105
The length of the returned object will be the same as the length of the
106-
*arg* argument. This is limited to 1024 bytes.
106+
*arg* argument.
107107

108108
If the :c:func:`fcntl` call fails, an :exc:`OSError` is raised.
109109

110110
.. note::
111-
If the type or the size of *arg* does not match the type or size
112-
of the argument of the operation (for example, if an integer is
111+
If the type or size of *arg* does not match the type or size
112+
of the operation's argument (for example, if an integer is
113113
passed when a pointer is expected, or the information returned in
114-
the buffer by the operating system is larger than 1024 bytes),
114+
the buffer by the operating system is larger than the size of *arg*
115+
and 1024 bytes),
115116
this is most likely to result in a segmentation violation or
116117
a more subtle data corruption.
117118

@@ -120,6 +121,7 @@ The module defines the following functions:
120121
.. versionchanged:: next
121122
Add support of arbitrary :term:`bytes-like objects <bytes-like object>`,
122123
not only :class:`bytes`.
124+
The size of bytes-like objects is no longer limited to 1024 bytes.
123125

124126

125127
.. function:: ioctl(fd, request, arg=0, mutate_flag=True, /)
@@ -157,8 +159,8 @@ The module defines the following functions:
157159
If the type or size of *arg* does not match the type or size
158160
of the operation's argument (for example, if an integer is
159161
passed when a pointer is expected, or the information returned in
160-
the buffer by the operating system is larger than 1024 bytes,
161-
or the size of the mutable bytes-like object is too small),
162+
the buffer by the operating system is larger than the size of *arg*
163+
and 1024 bytes),
162164
this is most likely to result in a segmentation violation or
163165
a more subtle data corruption.
164166

@@ -180,6 +182,8 @@ The module defines the following functions:
180182
.. versionchanged:: next
181183
The GIL is always released during a system call.
182184
System calls failing with EINTR are automatically retried.
185+
The size of not mutated bytes-like objects is no longer
186+
limited to 1024 bytes.
183187

184188
.. function:: flock(fd, operation, /)
185189

Lib/test/test_fcntl.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,52 @@ def test_fcntl_f_pipesize(self):
222222
os.close(test_pipe_r)
223223
os.close(test_pipe_w)
224224

225+
def _check_fcntl_not_mutate_len(self, nbytes=None):
226+
self.f = open(TESTFN, 'wb')
227+
buf = struct.pack('ii', fcntl.F_OWNER_PID, os.getpid())
228+
if nbytes is not None:
229+
buf += b' ' * (nbytes - len(buf))
230+
else:
231+
nbytes = len(buf)
232+
save_buf = bytes(buf)
233+
r = fcntl.fcntl(self.f, fcntl.F_SETOWN_EX, buf)
234+
self.assertIsInstance(r, bytes)
235+
self.assertEqual(len(r), len(save_buf))
236+
self.assertEqual(buf, save_buf)
237+
type, pid = memoryview(r).cast('i')[:2]
238+
self.assertEqual(type, fcntl.F_OWNER_PID)
239+
self.assertEqual(pid, os.getpid())
240+
241+
buf = b' ' * nbytes
242+
r = fcntl.fcntl(self.f, fcntl.F_GETOWN_EX, buf)
243+
self.assertIsInstance(r, bytes)
244+
self.assertEqual(len(r), len(save_buf))
245+
self.assertEqual(buf, b' ' * nbytes)
246+
type, pid = memoryview(r).cast('i')[:2]
247+
self.assertEqual(type, fcntl.F_OWNER_PID)
248+
self.assertEqual(pid, os.getpid())
249+
250+
buf = memoryview(b' ' * nbytes)
251+
r = fcntl.fcntl(self.f, fcntl.F_GETOWN_EX, buf)
252+
self.assertIsInstance(r, bytes)
253+
self.assertEqual(len(r), len(save_buf))
254+
self.assertEqual(bytes(buf), b' ' * nbytes)
255+
type, pid = memoryview(r).cast('i')[:2]
256+
self.assertEqual(type, fcntl.F_OWNER_PID)
257+
self.assertEqual(pid, os.getpid())
258+
259+
@unittest.skipUnless(
260+
hasattr(fcntl, "F_SETOWN_EX") and hasattr(fcntl, "F_GETOWN_EX"),
261+
"requires F_SETOWN_EX and F_GETOWN_EX")
262+
def test_fcntl_small_buffer(self):
263+
self._check_fcntl_not_mutate_len()
264+
265+
@unittest.skipUnless(
266+
hasattr(fcntl, "F_SETOWN_EX") and hasattr(fcntl, "F_GETOWN_EX"),
267+
"requires F_SETOWN_EX and F_GETOWN_EX")
268+
def test_fcntl_large_buffer(self):
269+
self._check_fcntl_not_mutate_len(2024)
270+
225271

226272
if __name__ == '__main__':
227273
unittest.main()

Lib/test/test_ioctl.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,8 @@ def test_ioctl_mutate_1024(self):
126126
self._check_ioctl_not_mutate_len(1024)
127127

128128
def test_ioctl_mutate_2048(self):
129-
# Test with a larger buffer, just for the record.
130129
self._check_ioctl_mutate_len(2048)
131-
self.assertRaises(ValueError, self._check_ioctl_not_mutate_len, 2048)
130+
self._check_ioctl_not_mutate_len(1024)
132131

133132

134133
@unittest.skipUnless(hasattr(os, 'openpty'), "need os.openpty()")
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Remove the 1024 bytes limit on the size of not mutated bytes-like argument
2+
in :func:`fcntl.fcntl` nad :func:`fcntl.ioctl`.

Modules/fcntlmodule.c

Lines changed: 78 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -88,25 +88,49 @@ fcntl_fcntl_impl(PyObject *module, int fd, int code, PyObject *arg)
8888
return NULL;
8989
}
9090
Py_ssize_t len = view.len;
91-
if (len > FCNTL_BUFSZ) {
92-
PyErr_SetString(PyExc_ValueError,
93-
"fcntl argument 3 is too long");
91+
if (len <= FCNTL_BUFSZ) {
92+
memcpy(buf, view.buf, len);
93+
buf[len] = '\0';
9494
PyBuffer_Release(&view);
95-
return NULL;
95+
96+
do {
97+
Py_BEGIN_ALLOW_THREADS
98+
ret = fcntl(fd, code, buf);
99+
Py_END_ALLOW_THREADS
100+
} while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
101+
if (ret < 0) {
102+
return !async_err ? PyErr_SetFromErrno(PyExc_OSError) : NULL;
103+
}
104+
return PyBytes_FromStringAndSize(buf, len);
96105
}
97-
memcpy(buf, view.buf, len);
98-
buf[len] = '\0';
99-
PyBuffer_Release(&view);
106+
else {
107+
PyObject *result = PyBytes_FromStringAndSize(NULL, len);
108+
if (result == NULL) {
109+
PyBuffer_Release(&view);
110+
return NULL;
111+
}
112+
void *ptr = PyBytes_AsString(result);
113+
memcpy(ptr, view.buf, len);
114+
PyBuffer_Release(&view);
100115

101-
do {
102-
Py_BEGIN_ALLOW_THREADS
103-
ret = fcntl(fd, code, buf);
104-
Py_END_ALLOW_THREADS
105-
} while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
106-
if (ret < 0) {
107-
return !async_err ? PyErr_SetFromErrno(PyExc_OSError) : NULL;
116+
do {
117+
Py_BEGIN_ALLOW_THREADS
118+
ret = fcntl(fd, code, ptr);
119+
Py_END_ALLOW_THREADS
120+
} while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
121+
if (ret < 0) {
122+
if (async_err) {
123+
PyErr_SetFromErrno(PyExc_OSError);
124+
}
125+
Py_DECREF(result);
126+
return NULL;
127+
}
128+
if (ptr[len] != '\0') {
129+
PyErr_SetString(PyExc_SystemError, "buffer overflow");
130+
return NULL;
131+
}
132+
return result;
108133
}
109-
return PyBytes_FromStringAndSize(buf, len);
110134
#undef FCNTL_BUFSZ
111135
}
112136
PyErr_Format(PyExc_TypeError,
@@ -239,25 +263,49 @@ fcntl_ioctl_impl(PyObject *module, int fd, unsigned long code, PyObject *arg,
239263
return NULL;
240264
}
241265
Py_ssize_t len = view.len;
242-
if (len > IOCTL_BUFSZ) {
243-
PyErr_SetString(PyExc_ValueError,
244-
"ioctl argument 3 is too long");
266+
if (len <= IOCTL_BUFSZ) {
267+
memcpy(buf, view.buf, len);
268+
buf[len] = '\0';
245269
PyBuffer_Release(&view);
246-
return NULL;
270+
271+
do {
272+
Py_BEGIN_ALLOW_THREADS
273+
ret = ioctl(fd, code, buf);
274+
Py_END_ALLOW_THREADS
275+
} while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
276+
if (ret < 0) {
277+
return !async_err ? PyErr_SetFromErrno(PyExc_OSError) : NULL;
278+
}
279+
return PyBytes_FromStringAndSize(buf, len);
247280
}
248-
memcpy(buf, view.buf, len);
249-
buf[len] = '\0';
250-
PyBuffer_Release(&view);
281+
else {
282+
PyObject *result = PyBytes_FromStringAndSize(NULL, len);
283+
if (result == NULL) {
284+
PyBuffer_Release(&view);
285+
return NULL;
286+
}
287+
void *ptr = PyBytes_AsString(result);
288+
memcpy(ptr, view.buf, len);
289+
PyBuffer_Release(&view);
251290

252-
do {
253-
Py_BEGIN_ALLOW_THREADS
254-
ret = ioctl(fd, code, buf);
255-
Py_END_ALLOW_THREADS
256-
} while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
257-
if (ret < 0) {
258-
return !async_err ? PyErr_SetFromErrno(PyExc_OSError) : NULL;
291+
do {
292+
Py_BEGIN_ALLOW_THREADS
293+
ret = ioctl(fd, code, ptr);
294+
Py_END_ALLOW_THREADS
295+
} while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
296+
if (ret < 0) {
297+
if (async_err) {
298+
PyErr_SetFromErrno(PyExc_OSError);
299+
}
300+
Py_DECREF(result);
301+
return NULL;
302+
}
303+
if (ptr[len] != '\0') {
304+
PyErr_SetString(PyExc_SystemError, "buffer overflow");
305+
return NULL;
306+
}
307+
return result;
259308
}
260-
return PyBytes_FromStringAndSize(buf, len);
261309
#undef IOCTL_BUFSZ
262310
}
263311
PyErr_Format(PyExc_TypeError,

0 commit comments

Comments
 (0)