From: ctm@ardi.com (Clifford T. Matthews)
Subject: Re: _IO, _IOR, _IOW and _IOWR functions
Keywords: pedantic, long
Organization: ARDI
Date: Wed, 19 Aug 92 22:09:46 GMT

Dear curious/bored folks,

The "routines" that the subject line alludes to are actually macros that
are used to create "magic cookies" for the ioctl system call.  By magic
cookie, I mean a unique integer identifier.  Here's a brief top-of-my-head
history of ioctls and such:

In general, UNIX allows you to do three different things with devices:
read, write, and "everything else".  Reading and writing need no further
explanation; "everything else" (done with the "ioctl" system call) varies
from device to device, but includes things like setting the input and output
speed for terminals, setting the density for tapes as well as werid stuff
like raw scsi commands (you need root to do these, which is good).

In general, UNIX has little abbreviations for the devices that it supports.
The NeXT has support for things like "sd" (SCSI drive), "od" (optical drive),
"zs" (zilog serial communications controler (terminal port)) and so on.  When
you need to do low-level accesses to these things you wind up writing code
that looks like:

	int filedescriptor;
	char buf[1024];

	filedescriptor = open("/dev/rsd0h", O_RDONLY);
	nread = read(filedescriptor, buf, sizeofbuff);
	...
	err = close(filedescriptor);
	...

In the above example "/dev/rsd0h" identifies that you want to open up
the first logical SCSI drive (drive 0).  The "h" means the special
partition that allows you to get to normally inaccessible portions of
the disk and the "r" preceeding the "sd" means you want "raw" access,
i.e. you'll be bypassing the normal UNIX block buffering cache.

You can read about raw SCSI access in the "sd" man page.  If you do, you
will see that you can do other things with disks, like eject them.
To do so, you use the DKIOCEJECT ioctl argument, as in:

	err = ioctl(filedescriptor, DKIOCEJECT, (char *) 0);

In the above example the third argument to the ioctl is zero, cast as a
pointer to char.  We do that because DKIOCEJECT doesn't need any extra
information to do it's job.  The file descriptor that is passed in is
sufficient to identify which disk to eject.  However, some ioctls need
extra information, or return extra information, or both.  We're now
ready to look at _IO, _IOR, _IOW and _IOWR.

It turns out that if you "grep DKIOCEJECT /usr/include/nextdev/*.h",
you'll see that DKIOEJECT is a #define that looks like this:

#define     DKIOCEJECT      _IO('d', 21)                    /* eject disk */

_IO is just a macro that generates a unique number that the implementors
of ioctl for disks can look at.  The 'd' is a single character that serves
to identify what kind of device the ioctl is relevant to 'd' in this case
stands for disk.  As long as people are consistent and there's no collision,
it doesn't matter what the letter is.  The second argument, 21, is just
a unique number that keeps DKIOCEJECT separate from the other 'd' ioctls.
If you "grep _IO /usr/include/nextdev/disk.h", you'll get this for output:

#define DKIOCGLABEL     _IO('d', 0)                     /* read label */
#define DKIOCSLABEL     _IO('d', 1)                     /* write label */
#define DKIOCGBITMAP    _IO('d', 2)                     /* read bitmap */
#define DKIOCSBITMAP    _IO('d', 3)                     /* write bitmap */
#define DKIOCREQ        _IOWR('d', 4, struct disk_req)  /* cmd request */
#define DKIOCINFO       _IOR('d', 5, struct drive_info) /* get drive info */
#define DKIOCZSTATS     _IO('d',7)                      /* zero statistics */
#define DKIOCGSTATS     _IO('d', 8)                     /* get statistics */
#define DKIOCRESET      _IO('d', 9)                     /* reset disk */
#define DKIOCGFLAGS     _IOR('d', 11, int)              /* get driver flags */
#define DKIOCSFLAGS     _IOW('d', 12, int)              /* set driver flags */
#define DKIOCSDCWIRE    _IOW('d', 14, struct sdc_wire)  /* sdc wire memory */
#define DKIOCSDCLOCK    _IO('d', 15)                    /* sdc lock */
#define DKIOCSDCUNLOCK  _IO('d', 16)                    /* sdc unlock */
#define DKIOCGFREEVOL   _IOR('d', 17, int)              /* get free volume # */
#define DKIOCGBBT       _IO('d', 18)                    /* read bad blk tbl */
#define DKIOCSBBT       _IO('d', 19)                    /* write bad blk tbl */
#define DKIOCMNOTIFY    _IOW('d', 20, int)              /* message on insert */
#define DKIOCEJECT      _IO('d', 21)                    /* eject disk */
#define DKIOCPANELPRT   _IOW('d', 22, int)              /* register Panel 
#define DKIOCSFORMAT    _IOW('d', 23, int)              /* set 'Formatted' flag 
#define DKIOCGFORMAT    _IOR('d', 23, int)              /* get 'Formatted' flag 

See, someone just arbitrarily assigned numbers for the various special things
that you may want to do to disks.  Not all of the #defines are _IO, there's
also _IOR, _IOW and _IOWR.  Those used when the ioctl needs to read information
from the device, write information to the device or both, respectively.
With those three variants of _IO, a little more is being done then creating
a unique number; the nunmber of bytes that need to be copied between
"user space" (i.e. your program) and "kernel space" (i.e. the UNIX operating
system) is also rolled into the "magic cookie".  Specifically those three
variants use sizeof(arg3) to determine how many bytes are going to need to
be transferred.  So in reality, the magic cookie consists of four things:

	The type of device we're operating with/on
	A unique number identifying the type of operation we're doing
	a pair of direction flags no direction, read, write, both
		and
	the number of bytes that will be moved.

So now the question is what can you do with _IO and friends?  Basically Nothing.
There is no point to rolling your own "magic cookies", because the device
drivers only know about the ioctls that are available through their (slightly)
less cryptic names (i.e. "DKIOCEJECT" vs. "_IO('d', 21)").  However, there
are rare times when you may want to know what kind of "ioctl"s an application
is using.  In that case, you can find the #defines for "_IO" and friends and
figure out what the various magic cookies for the various ioctl calls, and
then use gdb on the mystery program and take a look at the args on the stack
when ioctl is called.

As always, if a routine requires super-user privs to function, you should be
very wary of using it; when you're doing low-level accesses to disks and
what not, you can really do a lot of damage in a short time.

BTW, I enjoy learning about things via the net, but I really like to see
references rather than information; I try to do unto others ..., but in
this case I'm not really sure what a relevant reference would be, perhaps
a book about programming the kernel, or the design/history of UNIX.  The
trouble is, I was hacking UNIX kernels before those books were published,
so I'm not adequately familar with the books to tell you whether or not
they go into such little details.

	--Cliff[ord T. Matthews]
	ctm@ardi.com