Subject: dialin/dialout crapola
To: None <tech-kern@NetBSD.ORG>
From: Charles M. Hannum <mycroft@mit.edu>
List: tech-kern
Date: 03/21/1998 05:56:26
So, against my better judgement, I'm going to waste a bit more time
explaining the difference and implementation details between various
methods of handling dialin and dialout on the same port.


The `SunOS method' refers to having two device nodes in the file
system, which a bit in the minor number determining whether the
particular device node is for dialin or dialout use.

The `lock file method' requires all programs that use the port to
create UUCP-style lock files.

The `ISOPEN method' is what I referred to in my previous mail as the
`O_NONBLOCK method' (renamed here to distinguish it from other methods
that may also require the O_NONBLOCK flag).

I'm going to mostly avoid discussing the use of TIOCEXCL in 4.4BSD,
except to point out that all it really does in practice is make sure
that a non-root process doesn't try to use the same port; it's
primarily used to prevent someone mucking with a port used by lpd or a
refclock even if they have permissions to.  It's not really applicable
for shared dialin/dialout ports, and in fact probably shouldn't be
used in tip(1) at all.


So, first, an executive summary of how the 3 methods are used by
programs:

	    dialin			  dialout

SunOS	    fd = open("/dev/ttyXXX", 0);  fd = open("/dev/cuaXXX", 0);

lock file   start:			  start:
	    fd = open("/dev/ttyXXX", 0);  if (!create_lock_file())
	    if (!create_lock_file())	    sleep(N), goto start;
	      sleep(N), goto start;	  fd = open("/dev/ttyXXX", O_NONBLOCK);

ISOPEN      fd = open("/dev/ttyXXX", 0);  fd = open("/dev/ttyXXX", O_NONBLOCK);


There are several desirable properties of a particular locking method:

1) If the device is open for dialin, then opening the device for
   dialout should fail (with EBUSY).

   * For the SunOS method, this is done by random magic in the kernel.
     The implementation details aren't important; it's trivial to
     track whether the dialin device is open.

   * For the lock file method, this depends on whether we succeeded in
     acquiring the lock file.  A dialin user would have created a lock
     file after they succeeded in opening the device (possibly after
     waiting for carrier).

   * For the ISOPEN method, we fail if the open was non-blocking and
     ISOPEN was already set.  (This screws over `stty -f', though.)

2) If the device is open for dialout, then an attempt to open the
   device for dialin should wait (or fail, but e.g. getty really
   expects to just wait, and would have to poll periodically).

   * For the SunOS method, this is also easy to implement by random
     magic in the kernel.

   * For the lock file method, this is implemented by repeatedly
     trying to acquire the lock file.

   * For the ISOPEN method, if the open was blocking and ISOPEN is
     set, we wait.  (This is trivial to do in the same loop that
     already waits for carrier.)

3) If the device is already open for dialout, then another attempt to
   open it for dialout should fail (with EBUSY).

   * For the SunOS method, this is also easy to implement by random
     magic in the kernel.

   * For the lock file method, this depends on whether we succeeded in
     acquiring the lock file.  The first dialout user would have
     created a lock file.

   * For the ISOPEN method, we fail if the open was non-blocking and
     ISOPEN was already set.  (This screws over `stty -f', though.)

4) If the device is already open for dialin, then another attempt to
   open it for dialin should fail.

   * For the SunOS and ISOPEN methods, this is impossible, because
     there's no way to distinguish between another getty and a random
     process reopening the tty (which *must* be allowed).

   * The the lock file method, the dialin processes all use lock
     files, so the second one would just fail to acquire the lock.

5) When carrier is raised, if the device is open for dialout, then any
   dialin waiters should not be woken up.

   * For the SunOS method, more magic.

   * For the lock file method, they would succeed in the open(2), but
     fail to acquire the lock file, and would have to close the port
     and poll again.

   * For the ISOPEN method, the driver would not wake up processes
     waiting for carrier if ISOPEN is set.


So there are some tradeoffs:

* The SunOS method implements this as a black box in the kernel.  It
  uses extra device nodes, but it provides the desired semantics
  (except for preventing multiple gettys on the same port) without
  requiring a lot of magic in each program.

* The lock file method requires every program to know about lock
  files, which is annoying, especially for third party software.  It's
  also annoying that you're forced to poll the lock periodically.
  More importantly, though, you have to create a more or less
  free-for-all lock directory if you want to allow non-privileged
  processes to use dialin and dialout ports -- or, in the traditional
  solution, make all such programs setuid uucp.  This is unacceptable.

* The ISOPEN method has problems with things that don't really obey
  the locking semantics (like `stty -f'), unless we choose to elide
  some of those semantics; e.g. not caring about multiple simultaneous
  dialout users because `stty -f' looks like one and otherwise
  wouldn't work.  It probably works sufficiently to permit shared
  dialin and dialout use of a port provided you don't look at it
  funny.


Weighing the pros and cons, I have to say that the SunOS method looks
like the most attractive of the options.