[comp.bugs.4bsd] How to divorce a process from the controlling tty

bp@pixar.UUCP (Bruce Perens) (06/11/90)

In article <1990Jun8.070904.7466@athena.mit.edu> jik@athena.mit.edu (Jonathan I. Kamens) writes:
>
> Pulled from etc/syslogd.c:
>
>	if (!Debug) {
>		if (fork())
>			exit(0);
>		for (i = 0; i < 10; i++)
>			(void) close(i);
>		(void) open("/", 0);
>		(void) dup2(0, 1);
>		(void) dup2(0, 2);
>		untty();
>	}
>

keany@sequent.UUCP (Bernard Keany) writes:
>"Change the working directory to /.

Sorry, you didn't read the example carefully. There is no directory
changing going on in this code fragment.

The example is trying to divorce the process from its controlling
tty, so that it will not be hit by the side-effects of being associated
with a tty. To do this, it closes any fds that might be open to
a tty, and opens 0, 1, and 2 to _anything_ that it is sure is not a tty.
Then it calls untty(), which does an ioctl that clears the processes
controlling tty.

I think the usage of / for this is descended from a version of unix so
old that it did not have /dev/null . I don't believe in programs that
do this "because /dev/null might not exist". It might be that the programmer
wants the fd open to something that will cause an error on _write_, but
I don't buy that either. This code would fail if
some system didn't allow one to open / as a regular file, which I feel
is more likely than /dev/null going away, especially in the context of
network filesystems.

Some side effects from being associated with a controlling tty that
daemons want to avoid are:
	blocking on read or _write_, possibly forever.
	interrupt, quit, and other signals sent by the tty.
	shells mucking with your process group, and sending signals.
	(feel free to add to this list)

I the blocking part is more important than you might think. A while
ago I found the console of a 4.2 system locked up (by a spurious control-S)
and two dozen processes blocked on console writes.

Versions of unix that I have played with set the controlling tty this
way, your mileage may vary: If your parent has one, you get that one.
Otherwise, the first time you do an open on a tty, that becomes your
controlling tty.

Here's how I would divorce a process from a controlling tty. Feel
free to criticize and correct this:

	int	count;
	int	null_fd;
	int	tty_fd;
	/*
	 * Get rid of ALL open fds. Any one of them might be open
	 * to a tty.
	 */
	count = NFDS;
	do {
		close(--count);
	} while ( count );

	/*
	 * Open the traditional fds for a tty connection to
	 * something, to reduce the chance that they might be opened
	 * to a tty by some code later on.
	 */
	null_fd = open("/dev/null", 0);
#ifdef PARANOID
	if ( null_fd < 0 )
		null_fd = open("/", 0); /* /dev/null might not exist */
#endif
	dup2(0, 1);
	dup2(0, 2);

	/*
	 * Here's the fun part. See if there is still a controlling
	 * tty by opening /dev/tty. If you don't have a controlling
	 * tty, the open will fail! If it succeeds, do ioctl TIOCNOTTY
	 * on that fd, and close it again.
	 */
	 if ( (tty_fd = open("/dev/tty", 0)) >= 0 ) {
		ioctl(tty_fd, TIOCNOTTY, 0);
		close(tty_fd);
	 }

Now, don't go and open the console, or you might spoil everything.
Use syslog for logging.

Someone on the net will doubtless be able to correct my own misconceptions
and typos.
					Bruce Perens

jtkohl@MIT.EDU (John T Kohl) (06/12/90)

In article <11388@pixar.UUCP> bp@pixar.UUCP (Bruce Perens) writes:


> Here's how I would divorce a process from a controlling tty. Feel
> free to criticize and correct this:

> ...
> 	count = NFDS;
> ...

Modern Unixes have a call to tell you the # of file descriptors:

	count = getdtablesize();

--
John Kohl <jtkohl@ATHENA.MIT.EDU> or <jtkohl@MIT.EDU>
Digital Equipment Corporation/Project Athena
(The above opinions are MINE.  Don't put my words in somebody else's mouth!)

mar@athena.mit.edu (Mark A. Rosenstein) (06/13/90)

In article <11388@pixar.UUCP>, bp@pixar.UUCP (Bruce Perens) writes:
|> Here's how I would divorce a process from a controlling tty. Feel
|> free to criticize and correct this:
	[some code deleted]
|> 	/*
|> 	 * Here's the fun part. See if there is still a controlling
|> 	 * tty by opening /dev/tty. If you don't have a controlling
|> 	 * tty, the open will fail! If it succeeds, do ioctl TIOCNOTTY
|> 	 * on that fd, and close it again.
|> 	 */
|> 	 if ( (tty_fd = open("/dev/tty", 0)) >= 0 ) {
|> 		ioctl(tty_fd, TIOCNOTTY, 0);
|> 		close(tty_fd);
|> 	 }

Some code like that caused a heisenbug (you know, one where observing it
affects the outcome) here last year.  We were restarting our nameserver
regularly from a daemon.  If the daemon were restarted by someone netting
into the machine, then the daemon's controlling tty was a pseudo-tty.
When the nameserver got to the code fragment above, if no one was currently
logged in on that pseudo-tty, then it would hang.  When we noticed it
wasn't running and logged in to check on it, we would usually log in on
the correct pseudo-tty to get it running again and not be able to tell
that it had stopped.

I fixed this by including the non-blocking I/O flag in the open(), but
that requires a kernel mod from standard 4.3BSD for that to work.  Does
anyone know a better solution for this?
					-Mark

                Variables won't and constants aren't.

smb@ulysses.att.com (Steven Bellovin) (06/13/90)

In article <11388@pixar.UUCP>, bp@pixar.UUCP (Bruce Perens) writes:
> 
> I think the usage of / for this is descended from a version of unix so
> old that it did not have /dev/null . I don't believe in programs that
> do this "because /dev/null might not exist".

I'm not sure in what sense you ``don't believe in'' such programs.
They most certainly do exist....  And no, they're not from code
that old.  I don't know how old /dev/null is, but I assure you
it's much older than syslogd.

> It might be that the programmer
> wants the fd open to something that will cause an error on _write_, but
> I don't buy that either. This code would fail if
> some system didn't allow one to open / as a regular file, which I feel
> is more likely than /dev/null going away, especially in the context of
> network filesystems.

As I replied earlier by email to Jonathan Kamens, code like that
most certainly was intended to deal with /dev/null not being there.
And while I don't recall cases of /dev/null going away, I've seen
a fair number of instances of /dev/null having the wrong modes, or
somehow being replaced by a regular file.

There exists a school of thought, among some very serious wizards,
that says that / is the only thing you know for certain exists.
(Or rather, if it's not there, you're not likely to be able to do
much of anything anyway...)  The idea is to minimize the vulnerability
of the subsystem to other failures.

I'm not saying I agree or disagree -- I'm saying that the idea has
been propounded, by highly-qualified people.

		--Steve Bellovin

karl@haddock.ima.isc.com (Karl Heuer) (06/14/90)

In article <JTKOHL.90Jun11153217@lycus.MIT.EDU> jtkohl@MIT.EDU (John T Kohl) writes:
>Modern Unixes have a call to tell you the # of file descriptors:
>	count = getdtablesize();

Actually, in *modern* Unixes it's done with sysconf().