Skip to content

Commit e42daf4

Browse files
gh-132742: Refactor fcntl.fcntl() and fcntl.ioctl()
1 parent a35188d commit e42daf4

File tree

3 files changed

+140
-135
lines changed

3 files changed

+140
-135
lines changed

Doc/library/fcntl.rst

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,14 @@ The module defines the following functions:
8989
for *cmd* are operating system dependent, and are available as constants
9090
in the :mod:`fcntl` module, using the same names as used in the relevant C
9191
header files. The argument *arg* can either be an integer value, or a
92-
:class:`bytes` object, or a string.
92+
:term:`bytes-like object`, or a string.
9393
The type and the size of *arg* must match the type and the size of
9494
the argument of the operation as specified in the relevant C documentation.
9595

9696
With an integer value, the return value of this function is the integer
9797
return value of the C :c:func:`fcntl` call.
9898

99-
When the argument is bytes, it represents a binary structure,
99+
When the argument is a bytes-like object, it represents a binary structure,
100100
e.g. created by :func:`struct.pack`.
101101
A string value is encoded to binary using the UTF-8 encoding.
102102
The binary data is copied to a buffer whose address is
@@ -117,6 +117,10 @@ The module defines the following functions:
117117

118118
.. audit-event:: fcntl.fcntl fd,cmd,arg fcntl.fcntl
119119

120+
.. versionchanged:: next
121+
Add support of arbitrary :term:`bytes-like objects <bytes-like object>`,
122+
not only :class:`bytes`.
123+
120124

121125
.. function:: ioctl(fd, request, arg=0, mutate_flag=True, /)
122126

@@ -173,6 +177,9 @@ The module defines the following functions:
173177

174178
.. audit-event:: fcntl.ioctl fd,request,arg fcntl.ioctl
175179

180+
.. versionchanged:: next
181+
The GIL is always released during a system call.
182+
System calls failing with EINTR are automatically retries.
176183

177184
.. function:: flock(fd, operation, /)
178185

Modules/clinic/fcntlmodule.c.h

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/fcntlmodule.c

Lines changed: 125 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -54,57 +54,68 @@ static PyObject *
5454
fcntl_fcntl_impl(PyObject *module, int fd, int code, PyObject *arg)
5555
/*[clinic end generated code: output=888fc93b51c295bd input=7955340198e5f334]*/
5656
{
57-
unsigned int int_arg = 0;
5857
int ret;
59-
char *str;
60-
Py_ssize_t len;
61-
char buf[1024];
6258
int async_err = 0;
6359

6460
if (PySys_Audit("fcntl.fcntl", "iiO", fd, code, arg ? arg : Py_None) < 0) {
6561
return NULL;
6662
}
6763

68-
if (arg != NULL) {
69-
int parse_result;
70-
71-
if (PyArg_Parse(arg, "s#", &str, &len)) {
72-
if ((size_t)len > sizeof buf) {
73-
PyErr_SetString(PyExc_ValueError,
74-
"fcntl string arg too long");
64+
if (arg == NULL || PyIndex_Check(arg)) {
65+
unsigned int int_arg = 0;
66+
if (arg != NULL) {
67+
if (!PyArg_Parse(arg, "I", &int_arg)) {
7568
return NULL;
7669
}
77-
memcpy(buf, str, len);
78-
do {
79-
Py_BEGIN_ALLOW_THREADS
80-
ret = fcntl(fd, code, buf);
81-
Py_END_ALLOW_THREADS
82-
} while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
83-
if (ret < 0) {
84-
return !async_err ? PyErr_SetFromErrno(PyExc_OSError) : NULL;
85-
}
86-
return PyBytes_FromStringAndSize(buf, len);
8770
}
8871

89-
PyErr_Clear();
90-
parse_result = PyArg_Parse(arg,
91-
"I;fcntl requires a file or file descriptor,"
92-
" an integer and optionally a third integer or a string",
93-
&int_arg);
94-
if (!parse_result) {
95-
return NULL;
72+
do {
73+
Py_BEGIN_ALLOW_THREADS
74+
ret = fcntl(fd, code, (int)int_arg);
75+
Py_END_ALLOW_THREADS
76+
} while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
77+
if (ret < 0) {
78+
return !async_err ? PyErr_SetFromErrno(PyExc_OSError) : NULL;
9679
}
80+
return PyLong_FromLong(ret);
9781
}
82+
else if (PyUnicode_Check(arg) || PyObject_CheckBuffer(arg)) {
83+
#define FNCTL_BUFSZ 1024
84+
Py_buffer view;
85+
char buf[FNCTL_BUFSZ+1]; /* argument plus NUL byte */
9886

99-
do {
100-
Py_BEGIN_ALLOW_THREADS
101-
ret = fcntl(fd, code, (int)int_arg);
102-
Py_END_ALLOW_THREADS
103-
} while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
104-
if (ret < 0) {
105-
return !async_err ? PyErr_SetFromErrno(PyExc_OSError) : NULL;
87+
if (!PyArg_Parse(arg, "s*", &view)) {
88+
return NULL;
89+
}
90+
Py_ssize_t len = view.len;
91+
if (len > FNCTL_BUFSZ) {
92+
PyErr_SetString(PyExc_ValueError,
93+
"fcntl argument 3 is too long");
94+
PyBuffer_Release(&view);
95+
return NULL;
96+
}
97+
memcpy(buf, view.buf, len);
98+
buf[len] = '\0';
99+
PyBuffer_Release(&view);
100+
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;
108+
}
109+
return PyBytes_FromStringAndSize(buf, len);
110+
#undef FNCTL_BUFSZ
111+
}
112+
else {
113+
PyErr_Format(PyExc_TypeError,
114+
"fcntl(): argument 3 must be an integer, "
115+
"a bytes-like object, or a string, not %T",
116+
arg);
117+
return NULL;
106118
}
107-
return PyLong_FromLong((long)ret);
108119
}
109120

110121

@@ -113,7 +124,7 @@ fcntl.ioctl
113124
114125
fd: fildes
115126
request as code: unsigned_long(bitwise=True)
116-
arg as ob_arg: object(c_default='NULL') = 0
127+
arg: object(c_default='NULL') = 0
117128
mutate_flag as mutate_arg: bool = True
118129
/
119130
@@ -148,11 +159,10 @@ code.
148159
[clinic start generated code]*/
149160

150161
static PyObject *
151-
fcntl_ioctl_impl(PyObject *module, int fd, unsigned long code,
152-
PyObject *ob_arg, int mutate_arg)
153-
/*[clinic end generated code: output=3d8eb6828666cea1 input=cee70f6a27311e58]*/
162+
fcntl_ioctl_impl(PyObject *module, int fd, unsigned long code, PyObject *arg,
163+
int mutate_arg)
164+
/*[clinic end generated code: output=f72baba2454d7a62 input=9c6cca5e2c339622]*/
154165
{
155-
#define IOCTL_BUFSZ 1024
156166
/* We use the unsigned non-checked 'I' format for the 'code' parameter
157167
because the system expects it to be a 32bit bit field value
158168
regardless of it being passed as an int or unsigned long on
@@ -163,114 +173,102 @@ fcntl_ioctl_impl(PyObject *module, int fd, unsigned long code,
163173
in their unsigned long ioctl codes this will break and need
164174
special casing based on the platform being built on.
165175
*/
166-
int arg = 0;
167176
int ret;
168-
Py_buffer pstr;
169-
char *str;
170-
Py_ssize_t len;
171-
char buf[IOCTL_BUFSZ+1]; /* argument plus NUL byte */
177+
int async_err = 0;
172178

173-
if (PySys_Audit("fcntl.ioctl", "ikO", fd, code,
174-
ob_arg ? ob_arg : Py_None) < 0) {
179+
if (PySys_Audit("fcntl.ioctl", "ikO", fd, code, arg ? arg : Py_None) < 0) {
175180
return NULL;
176181
}
177182

178-
if (ob_arg != NULL) {
179-
if (PyArg_Parse(ob_arg, "w*:ioctl", &pstr)) {
180-
char *arg;
181-
str = pstr.buf;
182-
len = pstr.len;
183-
184-
if (mutate_arg) {
185-
if (len <= IOCTL_BUFSZ) {
186-
memcpy(buf, str, len);
187-
buf[len] = '\0';
188-
arg = buf;
183+
if (arg == NULL || PyIndex_Check(arg)) {
184+
int int_arg = 0;
185+
if (arg != NULL) {
186+
if (!PyArg_Parse(arg, "i", &int_arg)) {
187+
return NULL;
188+
}
189+
}
190+
191+
do {
192+
Py_BEGIN_ALLOW_THREADS
193+
ret = ioctl(fd, code, int_arg);
194+
Py_END_ALLOW_THREADS
195+
} while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
196+
if (ret < 0) {
197+
return !async_err ? PyErr_SetFromErrno(PyExc_OSError) : NULL;
198+
}
199+
return PyLong_FromLong(ret);
200+
}
201+
else if (PyUnicode_Check(arg) || PyObject_CheckBuffer(arg)) {
202+
Py_buffer view;
203+
#define IOCTL_BUFSZ 1024
204+
char buf[IOCTL_BUFSZ+1]; /* argument plus NUL byte */
205+
if (mutate_arg && !PyBytes_Check(arg) && !PyUnicode_Check(arg)) {
206+
if (PyObject_GetBuffer(arg, &view, PyBUF_WRITABLE) == 0) {
207+
if (view.len <= IOCTL_BUFSZ) {
208+
memcpy(buf, view.buf, view.len);
209+
buf[view.len] = '\0';
210+
do {
211+
Py_BEGIN_ALLOW_THREADS
212+
ret = ioctl(fd, code, buf);
213+
Py_END_ALLOW_THREADS
214+
} while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
215+
memcpy(view.buf, buf, view.len);
189216
}
190217
else {
191-
arg = str;
218+
do {
219+
Py_BEGIN_ALLOW_THREADS
220+
ret = ioctl(fd, code, view.buf);
221+
Py_END_ALLOW_THREADS
222+
} while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
192223
}
193-
}
194-
else {
195-
if (len > IOCTL_BUFSZ) {
196-
PyBuffer_Release(&pstr);
197-
PyErr_SetString(PyExc_ValueError,
198-
"ioctl string arg too long");
224+
if (ret < 0) {
225+
if (!async_err) {
226+
PyErr_SetFromErrno(PyExc_OSError);
227+
}
228+
PyBuffer_Release(&view);
199229
return NULL;
200230
}
201-
else {
202-
memcpy(buf, str, len);
203-
buf[len] = '\0';
204-
arg = buf;
205-
}
206-
}
207-
if (buf == arg) {
208-
Py_BEGIN_ALLOW_THREADS /* think array.resize() */
209-
ret = ioctl(fd, code, arg);
210-
Py_END_ALLOW_THREADS
211-
}
212-
else {
213-
ret = ioctl(fd, code, arg);
214-
}
215-
if (mutate_arg && (len <= IOCTL_BUFSZ)) {
216-
memcpy(str, buf, len);
217-
}
218-
if (ret < 0) {
219-
PyErr_SetFromErrno(PyExc_OSError);
220-
PyBuffer_Release(&pstr);
221-
return NULL;
222-
}
223-
PyBuffer_Release(&pstr);
224-
if (mutate_arg) {
231+
PyBuffer_Release(&view);
225232
return PyLong_FromLong(ret);
226233
}
227-
else {
228-
return PyBytes_FromStringAndSize(buf, len);
234+
if (!PyErr_ExceptionMatches(PyExc_BufferError)) {
235+
return NULL;
229236
}
237+
PyErr_Clear();
230238
}
231239

232-
PyErr_Clear();
233-
if (PyArg_Parse(ob_arg, "s*:ioctl", &pstr)) {
234-
str = pstr.buf;
235-
len = pstr.len;
236-
if (len > IOCTL_BUFSZ) {
237-
PyBuffer_Release(&pstr);
238-
PyErr_SetString(PyExc_ValueError,
239-
"ioctl string arg too long");
240-
return NULL;
241-
}
242-
memcpy(buf, str, len);
243-
buf[len] = '\0';
240+
if (!PyArg_Parse(arg, "s*", &view)) {
241+
return NULL;
242+
}
243+
Py_ssize_t len = view.len;
244+
if (len > IOCTL_BUFSZ) {
245+
PyErr_SetString(PyExc_ValueError,
246+
"ioctl argument 3 is too long");
247+
PyBuffer_Release(&view);
248+
return NULL;
249+
}
250+
memcpy(buf, view.buf, len);
251+
buf[len] = '\0';
252+
PyBuffer_Release(&view);
253+
254+
do {
244255
Py_BEGIN_ALLOW_THREADS
245256
ret = ioctl(fd, code, buf);
246257
Py_END_ALLOW_THREADS
247-
if (ret < 0) {
248-
PyErr_SetFromErrno(PyExc_OSError);
249-
PyBuffer_Release(&pstr);
250-
return NULL;
251-
}
252-
PyBuffer_Release(&pstr);
253-
return PyBytes_FromStringAndSize(buf, len);
254-
}
255-
256-
PyErr_Clear();
257-
if (!PyArg_Parse(ob_arg,
258-
"i;ioctl requires a file or file descriptor,"
259-
" an integer and optionally an integer or buffer argument",
260-
&arg)) {
261-
return NULL;
258+
} while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
259+
if (ret < 0) {
260+
return !async_err ? PyErr_SetFromErrno(PyExc_OSError) : NULL;
262261
}
263-
// Fall-through to outside the 'if' statement.
262+
return PyBytes_FromStringAndSize(buf, len);
263+
#undef IOCTL_BUFSZ
264264
}
265-
Py_BEGIN_ALLOW_THREADS
266-
ret = ioctl(fd, code, arg);
267-
Py_END_ALLOW_THREADS
268-
if (ret < 0) {
269-
PyErr_SetFromErrno(PyExc_OSError);
265+
else {
266+
PyErr_Format(PyExc_TypeError,
267+
"ioctl(): argument 3 must be an integer, "
268+
"a bytes-like object, or a string, not %T",
269+
arg);
270270
return NULL;
271271
}
272-
return PyLong_FromLong((long)ret);
273-
#undef IOCTL_BUFSZ
274272
}
275273

276274
/*[clinic input]

0 commit comments

Comments
 (0)