[net.unix-wizards] UUCP and line turn around

dan (08/13/82)

	While I have seen a moderate amount of discussion on the net on
how to use a single comm line for both dialin and dialout (line turn around),
I have not noticed a concensus on a clean way to accomplish it. If there has
been such a concensus, maybe someone could mail me the details.
	Anyways, I offer this scheme for comment and evaluation...

	Basically, the scheme involves a lock bit associated with each
tty struct. It seems that there are (at least) three basic and different types
of processes/situations which need to be covered in a line turn around scheme:
	1) init/getty dialin processes which block on open() waiting for a
		carrier, perform read() and when a reasonable logname is found,
		exec login(1) and then sh(1) or perhaps uucico. Thus the line
		should only be locked when login(1) is satisfied and the line
		should be unlocked when sh(1) or uucico has retired.
	2) cu/uucp dialout processes which really need the line at the moment
		of execution and should lock the line at the open() time.
		They should obviously free the line afterwards and honor the
		lock if a dialin process already logged in.
	3) write(1) and other user programs which should not block or be
		bothered with the lock (this assumes that write(1) is using
		another locking mechanism such as the write permission bit),
		since one should be able to write(1) to a user logged in on
		the dialup line.

	So...
	1) There is a lock bit associated with each line (tty struct)
	2) There are routines which set and clear this bit, callable via
		stty()/ioctl(); we will call them ttlock() and ttunlock().
	3) ttlock() first checks if the line is locked and sleeps until
		the lock bit is unset. Then the lock bit is set and
		a SIGHUP is sent to the current t_pgrp process group,
		and the calling p_pgrp is made t_pgrp.
	4) ttunlock() clears the lock bit and calls wakeup() for pending
		ttlock()'s.
	5) ttyclose() calls ttunlock() if the calling p_pgrp is t_pgrp.
		Otherwise, if the lock bit is set, ttyclose() aborts the
		close.
	6) ttyopen() blocks if the lock bit is set and the process is
		init/getty-like (p_pgrp == 0, setting up a new t_pgrp).
	7) ttyopen() calls ttlock() when the line has execute privilege
		on its inode (most tty's are mode 0622, which /etc/init
		resets).
	8) the dialout line (e.g. /dev/cul0) is a different inode
		(i.e. not a link) than the corresponding /dev/tty? for
		dialin. /dev/cul? have the execute bit set,
		(e.g. chmod 777). Since they are different inodes, they
		can have different permission bits and different i_count
		for closes on the devices.
	9) login(1) calls ttlock() when the password has been satisfied and
		it is about to exec the shell, whether sh(1) or uucico.

	Thus the normal sequence of events is:
	- init/getty calls open() and either block on the carrier, or if
		e.g. its a direct connection, proceed to read().
		- if a dialin is in progress, login calls ttlock.
			User programs such as write(1) aren't locked out,
			since the /dev/tty? entry doesn't have the x bit
			set and they are not like init/getty.
			cu and uucico are blocked since they use
			/dev/cul? which do have the x bit set.
		- when the dialin completely, the ttyclose() calls
			ttunlock. Also we run an accounting program
			at the end of every login session which calls
			ttunlock() also.

		- if a dialout occurs before login(1) calls ttlock(),
			the init/getty gets SIGHUP'ed and then the new
			one gets blocked in ttyopen(), while the dialout
			continues.
		- when the dialout completes, the ttyclose() does the
			ttunlock(), which does the wakeup for init/getty.
			ttyclose() does get called since the dialout uses
			a different inode entry with its own i_count.
			Though it may not be necessary, I have the init/getty
			return after the lockout with EBUSY, so the open()
			fails and init tries open() again.

	Actually the code is short, maybe 30-40 lines total, in only
tty.c, and login.c. No other programs need reworking, in particular, uucp, cu.

	Problems:
	1) killing init/getty. Alternatives are to block its read()'s
		(ugly) or somehow disconnect its logical line from the
		physical line (someday).
	2) background procs. Procs which survive the logout will probably
		have a handle on the line which will inhibit the ttyclose()
		preventing the ttunlock(). We don't allow users to run
		procs after they've logged out (except through an at(1)-like
		batch facility) since passwords can be stolen, etc.
		Also our shell execs an accounting program
		at logout time which does ttunlock().
		The remaining situation is /etc/rc, which is also easy for
		us. We just redirect the stdin, stdout and stderr of
		/etc/rc procs to /dev/tty which our shell allows in one
		statement in the beginning of /etc/rc.

	The above scheme has been working for a short time on our system.
I welcome any comments and suggestions.

				...decvax!cca!ima!n44a!dan

				Dan Ts'o

johnl (08/13/82)

Dan Ts'o's method of turning around a line for use as both dial in and
dial out strikes me as being somewhat overimplemented.  Here's how I
accomplished it with less work:

    1.  You have programs "enable" and "disable" that edit /etc/ttys
	and poke the init process.  They have been on Usenix tapes before
	and are easy to write in any event.

    2.  Modify /etc/getty so that it deletes /usr/spool/uucp/LCK..ttyX
        whenever it starts up on line X (because the line is now free.)
        Have it create that file when a user starts to log in, e.g. when
        it gets the first character of the login name.  You might have a
        file that identifies lines that are subject to turn-around, to
	avoid useless lock files.  If your version of /etc/init opens the
	tty before calling getty, you'll have to delete the lock file in
	init.

    3.  Modify uucp (and cu if you use it) so that when it creates the
	lock file it looks in /etc/ttys, and if the line is enabled, it
	disables it.  Similarly, when it deletes the lock file, it should
	re-enable the line if it previously disabled it.

That's all you need to do.  Works fine for me.  Dan's method avoided
changing uucp, but involved changes to the kernel.  This method keeps it
all in user mode.

martin (08/21/82)

ima!johnl method works fine but i dont see why you should have to call
enable/disable or even touch the /etc/ttys file. i do this in a different
way, buy checking the lock status in getty.c after the open().

	getty()
	{
		unlock();
		open(tty_name);
		if( locked() )
			forget();
		lock();
		...
		...
	}

uucp has no changes, it produces the lock file and then opens the line (which
will wait for the acu to dial etc etc).

the getty never returns from the forget() call, all that does is wait to be
killed by a SIGHUP, or it checks the LCK.. file every 60 secs.

martin levy, bell labs, holmdel.