[ 555817 ] Flawed fcntl.ioctl implementation.

with my patch that allows for an array to be mutated when passed
as the buffer argument to ioctl() (details complicated by
backwards compatibility considerations -- read the docs!).
This commit is contained in:
Michael W. Hudson 2003-03-03 12:29:42 +00:00
parent 122152451e
commit f008998668
4 changed files with 171 additions and 18 deletions

View File

@ -47,10 +47,57 @@ The module defines the following functions:
raised.
\end{funcdesc}
\begin{funcdesc}{ioctl}{fd, op, arg}
This function is identical to the \function{fcntl()} function, except
that the operations are typically defined in the library module
\refmodule{termios}.
\begin{funcdesc}{ioctl}{fd, op\optional{, arg\optional{, mutate_flag}}}
This function is identical to the \function{fcntl()} function,
except that the operations are typically defined in the library
module \refmodule{termios} and the argument handling is even more
complicated.
The parameter \var{arg} can be one of an integer, absent (treated
identically to the integer \code{0}), an object supporting the
read-only buffer interface (most likely a plain Python string) or an
object supporting the read-write buffer interface.
In all but the last case, behaviour is as for the \function{fcntl()}
function.
If a mutable buffer is passed, then the behaviour is determined by
the value of the \var{mutate_flag} parameter.
If it is false, the buffer's mutability is ignored and behaviour is
as for a read-only buffer, except that the 1024 byte limit mentioned
above is avoided -- so long as the buffer you pass is longer than
what the operating system wants to put there, things should work.
If \var{mutate_flag} is true, then the buffer is (in effect) passed
to the underlying \function{ioctl()} system call, the latter's
return code is passed back to the calling Python, and the buffer's
new contents reflect the action of the \function{ioctl}. This is a
slight simplification, because if the supplied buffer is less than
1024 bytes long it is first copied into a static buffer 1024 bytes
long which is then passed to \function{ioctl} and copied back into
the supplied buffer.
If \var{mutate_flag} is not supplied, then in 2.3 it defaults to
false. This is planned to change over the next few Python versions:
in 2.4 failing to supply \var{mutate_flag} will get a warning but
the same behavior and in versions later than 2.5 it will default to
true.
An example:
\begin{verbatim}
>>> import array, fnctl, struct, termios, os
>>> os.getpgrp()
13341
>>> struct.unpack('h', fcntl.ioctl(0, termios.TIOCGPGRP, " "))[0]
13341
>>> buf = array.array('h', [0])
>>> fcntl.ioctl(0, termios.TIOCGPGRP, buf, 1)
0
>>> buf
array('h', [13341])
\end{verbatim}
\end{funcdesc}
\begin{funcdesc}{flock}{fd, op}
@ -122,7 +169,7 @@ better.
\begin{seealso}
\seemodule{os}{The \function{os.open} function supports locking flags
and is available on a wider variety of platforms than
the \function{fcntl.lockf} and \function{fcntl.flock}
functions, providing a more platform-independent file
locking facility.}
the \function{fcntl.lockf} and \function{fcntl.flock}
functions, providing a more platform-independent file
locking facility.}
\end{seealso}

31
Lib/test/test_ioctl.py Normal file
View File

@ -0,0 +1,31 @@
import unittest
from test_support import TestSkipped, run_unittest
import os, struct
try:
import fcntl, termios
except ImportError:
raise TestSkipped("No fcntl or termios module")
if not hasattr(termios,'TIOCGPGRP'):
raise TestSkipped("termios module doesn't have TIOCGPGRP")
class IoctlTests(unittest.TestCase):
def test_ioctl(self):
pgrp = os.getpgrp()
tty = open("/dev/tty", "r")
r = fcntl.ioctl(tty, termios.TIOCGPGRP, " ")
self.assertEquals(pgrp, struct.unpack("i", r)[0])
def test_ioctl_mutate(self):
import array
buf = array.array('i', [0])
pgrp = os.getpgrp()
tty = open("/dev/tty", "r")
r = fcntl.ioctl(tty, termios.TIOCGPGRP, buf, 1)
self.assertEquals(r, 0)
self.assertEquals(pgrp, buf[0])
def test_main():
run_unittest(IoctlTests)
if __name__ == "__main__":
test_main()

View File

@ -24,6 +24,9 @@ Core and builtins
Extension modules
-----------------
- Modified the fcntl.ioctl() function to allow modification of a passed
mutable buffer (for details see the reference documentation).
- Made user requested changes to the itertools module.
Subsumed the times() function into repeat().
Added chain() and cycle().

View File

@ -99,8 +99,62 @@ fcntl_ioctl(PyObject *self, PyObject *args)
int ret;
char *str;
int len;
int mutate_arg = 0;
char buf[1024];
if (PyArg_ParseTuple(args, "O&iw#|i:ioctl",
conv_descriptor, &fd, &code,
&str, &len, &mutate_arg)) {
char *arg;
if (PyTuple_Size(args) == 3) {
/* warning goes here in 2.4 */
mutate_arg = 0;
}
if (mutate_arg) {
if (len <= sizeof buf) {
memcpy(buf, str, len);
arg = buf;
}
else {
arg = str;
}
}
else {
if (len > sizeof buf) {
PyErr_SetString(PyExc_ValueError,
"ioctl string arg too long");
return NULL;
}
else {
memcpy(buf, str, len);
arg = buf;
}
}
if (buf == arg) {
Py_BEGIN_ALLOW_THREADS /* think array.resize() */
ret = ioctl(fd, code, arg);
Py_END_ALLOW_THREADS
}
else {
ret = ioctl(fd, code, arg);
}
if (mutate_arg && (len < sizeof buf)) {
memcpy(str, buf, len);
}
if (ret < 0) {
PyErr_SetFromErrno(PyExc_IOError);
return NULL;
}
if (mutate_arg) {
return PyInt_FromLong(ret);
}
else {
return PyString_FromStringAndSize(buf, len);
}
}
PyErr_Clear();
if (PyArg_ParseTuple(args, "O&is#:ioctl",
conv_descriptor, &fd, &code, &str, &len)) {
if (len > sizeof buf) {
@ -123,7 +177,7 @@ fcntl_ioctl(PyObject *self, PyObject *args)
arg = 0;
if (!PyArg_ParseTuple(args,
"O&i|i;ioctl requires a file or file descriptor,"
" an integer and optionally a third integer or a string",
" an integer and optionally a integer or buffer argument",
conv_descriptor, &fd, &code, &arg)) {
return NULL;
}
@ -138,17 +192,35 @@ fcntl_ioctl(PyObject *self, PyObject *args)
}
PyDoc_STRVAR(ioctl_doc,
"ioctl(fd, opt, [arg])\n\
"ioctl(fd, opt[, arg[, mutate_flag]])\n\
\n\
Perform the requested operation on file descriptor fd. The operation\n\
is defined by op and is operating system dependent. Typically these\n\
codes can be retrieved from the library module IOCTL. The argument arg\n\
is optional, and defaults to 0; it may be an int or a string. If arg is\n\
given as a string, the return value of ioctl is a string of that length,\n\
containing the resulting value put in the arg buffer by the operating system.\n\
The length of the arg string is not allowed to exceed 1024 bytes. If the arg\n\
given is an integer or if none is specified, the result value is an integer\n\
corresponding to the return value of the ioctl call in the C code.");
Perform the requested operation on file descriptor fd. The operation is\n\
defined by op and is operating system dependent. Typically these codes are\n\
retrieved from the fcntl or termios library modules.\n\
\n\
The argument arg is optional, and defaults to 0; it may be an int or a\n\
buffer containing character data (most likely a string or an array). \n\
\n\
If the argument is a mutable buffer (such as an array) and if the\n\
mutate_flag argument (which is only allowed in this case) is true then the\n\
buffer is (in effect) passed to the operating system and changes made by\n\
the OS will be reflected in the contents of the buffer after the call has\n\
returned. The return value is the integer returned by the ioctl system\n\
call.\n\
\n\
If the argument is a mutable buffer and the mutable_flag argument is not\n\
passed or is false, the behavior is as if a string had been passed. This\n\
behavior will change in future releases of Python.\n\
\n\
If the argument is an immutable buffer (most likely a string) then a copy\n\
of the buffer is passed to the operating system and the return value is a\n\
string of the same length containing whatever the operating system put in\n\
the buffer. The length of the arg buffer in this case is not allowed to\n\
exceed 1024 bytes.\n\
\n\
If the arg given is an integer or if none is specified, the result value is\n\
an integer corresponding to the return value of the ioctl call in the C\n\
code.");
/* flock(fd, operation) */