From: utzoo!decvax!cca!ima!n44a!dan
Newsgroups: net.unix-wizards
Title: UUCP and line turn around
Article-I.D.: n44a.111
Posted: Thu Aug 12 21:23:08 1982
Received: Tue Aug 17 07:07:10 1982


	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