[mod.std.unix] problem with the iocntl proposal

std-unix@ut-sally.UUCP (Moderator, John Quarterman) (10/31/86)

From: cbosgd!cbosgd.ATT.COM!mark@ucbvax.berkeley.edu (Mark Horton)
Date: 28 Oct 86 03:12:38 GMT
Organization: AT&T Bell Laboratories, Columbus, Oh

The iocntl proposal, as stated in section C.3 of the POSIX book,
has a bug.  For those who don't have the book handy, it defines
	iocntl(fildes, command, mask, argp, argpsize)
where command specifies the unique name of the control; mask is
either IO_READ, IO_WRITE, or IO_READ|IO_WRITE; argp is a pointer
to the argument, and argpsize is the number of bytes in the argument.

It proposes implementation of upward compatibility
with ioctl with a define such as
	#define ioctl(a, b, c) iocntl(a, b, c, sizeof(c))
and using a two ident define for b, thus
	#define IO_READ		1
	#define IO_WRITE	2
	#define TERM_MASK	('T'<<8)
	#define SOME_MASK	(IO_READ|IO_WRITE),(TERM_MASK | 0)
	...
	struct foo *ptr = ...
	ioctl(0, SOME_MASK,ptr);

The bug here is that the sizeof is taken on the pointer, not the
item itself, so it will always be 4 on a typical 32 bit machine.

One obvious fix might change the spec to read
	#define ioctl(a, b, c) iocntl(a, b, c, sizeof(*c))
This might work for many cases.  But there are two cases for
which it fails:

(1) ioctls where the object pointed to is an array.  In particular
    if the argument is a character string, as in SVr3's I_PUSH ioctl
    to push a streams module, this won't work.
(2) ioctls where the argument value itself, rather than what's at
    the end of a pointer, is passed.  System V does this a lot;
    see, for example, TCSBRK, TCXONC, and TCFLSH.

One possible solution to this problem would be to rearrange the
order of the arguments to iocntl.  If it becomes

	iocntl(fildes, command, mask, argpsize, argp)

then a trick similar to the one used in 4.2BSD becomes possible:

	#define ioctl iocntl
	#define IO_READ		1
	#define IO_WRITE	2
	#define TERM_MASK ('T'<<8)
	#define SOME_MASK (IO_READ|IO_WRITE),(TERM_MASK | 0),sizeof (struct foo)
	...
	struct foo *ptr = ...
	ioctl(0, SOME_MASK, ptr);

Another possibility is similar, but closer to what 4.2 does.  We
define iocntl as a function that takes three parameters, just like
ioctl, but the second parameter is a long, and we get

	iocntl(fildes, (long) command, argp)

	#define ioctl iocntl
	#define IO_READ		(1<<14)
	#define IO_WRITE	(2<<14)
	#define TERM_MASK ('T'<<24)
	#define SOME_MASK (long) (IO_READ|IO_WRITE)|(TERM_MASK | 0)|sizeof (struct foo)
	...
	struct foo *ptr = ...
	ioctl(0, SOME_MASK, ptr);

As I understand it, the only reason for playing these games, instead of
just doing what 4.2 did and leaving it called ioctl, is that ioctl is
defined to take an int as the 2nd parameter, and this method has to pass
a long.  It works on a 32 bit machine, but not on a 16 bit CPU such as
an 80286 (unless you implement "int" as 32 bits.)  Am I missing something?

These comments apply to termcntl as well as iocntl.

By the way, if this proposal is going to be used, please let's change
the name from iocntl to something that doesn't *sound* the same as
ioctl.  Otherwise there will be lots of confusion.

	Mark

Volume-Number: Volume 8, Number 9