Proper way to detach from control terminal?

Larry McVoy [Contractor] lm at sun.uucp
Mon Jan 30 10:48:33 AEST 1989


In article <5333 at brspyr1.BRS.Com> tim at brspyr1.BRS.Com (Tim Northrup) writes:
>I would like to know if there is a "proper" way to detach a process from
>its   control   terminal  (once  it  has  fork'ed  and  the  parent  has
>terminated).  I have seen a couple of different ways of doing  this  and
>would  like  to  know  which  ones  work,  which don't, if there are any
>others, and which is the correct way to do it.
>
>Here are the methods I have seen:
>
>	1.  setpgrp(2)
>
>                This  is  the  method  described in the System V section
>                termio(7), and does seem to work.  The calling  sequence
>                I  am  using  is  simply  setpgrp()  for  System  V  and
>                setpgrp(0,getpid()) for BSD based  code.   But,  on  BSD
>                systems,  the  control terminal is still reported when a
>                ps(1) is done.  Under  System  V,  the  control  tty  is
>                listed  as  '?', as I would expect.  Is there any way to
>                get this behavior under BSD?

This is good for System5.3 and earlier models.  This is bad for BSD systems.
See the ``whole story'' below.

>	2.  ioctl(...TIOCSPGRP...)
>
>		Is using ioctl to set the process group any different
>		than using the setpgrp() system call?

This is different from above in that it sets the ttys' pgrp - not yours.

>	3.  ioctl(...TIOCNOTTY...)
>
>		I have seen this used in the recently posted plp
>		software, but cannot find any documentation on this
>		setting other than "void tty association" in the
>		header file <sys/ioctl.h> on a BSD system.

This is best for BSD systems.

>	4.  closing all terminal files
>
>                I  don't know if I am reading things wrong, but it seems
>                that some programs simple close  stdin/stdout/stderr  on
>                startup  and  open  something  else as stdin.  Does this
>                really do anything?  Should I do this  in  concert  with
>                any/all of the above options?

This does not do what you want.

===============================================================================

    I've been working on this stuff for the last month while POSIXifying 
SunOS.  I think I have a handle on it & it's a mess.  You do different things
depending upon which system you are on.  Suppose we have the following
manifests:

SYS_5_3		/* all System5 like versions up to 5.3 */
BSD_4_3		/* all BSD systems up to 4.3 & 2.10 (not sure on 2.x) */
POSIX		/* any POSIX conforming system, i.e. BSD 4.4,Sys5.4,SunOS4.1 */

and suppose that you have a daemon that you wish to disassociate from the 
ctty:

#if defined(POSIX)
	/*
	 * setsid will give me a new session w/o any tty associated at all
	 */
	setsid();
#elif defined(BSD_4_3)
	/*
	 * TIOCNOTTY will get rid of my tty & set my pgrp to 0
	 */
	ioctl(0, TIOCNOTTY, 0);
#elif defined(SYS_5_3)
	/*
	 * system 5 setpgrp is very similar to POSIX setsid
	 */
	setpgrp();
# endif

The problem comes when you don't want a ctty but you do want to talk to a tty.
Open() has the unfortunate side effect of handing out cttys' even when you
don't want one.  You can do the following in the specified environment:

BSD4.3 & earlier:
	TIOCNOTTY on a fd that is talking to your tty.  This is commonly done
	like this:

		if ((fd = open("'dev/tty", 2)) != -1) {
		    ioctl(fd, TIOCNOTTY, 0);
		    close(fd);
		}

System5.3 & earlier:
	Do a setpgrp().

POSIX:
	Do a setsid().

===============================================================================


The whole story:

    The system keeps track of tty's in several places; the u area, the open
file table, and in the tty driver.  
    
    Open file table: 
	doesn't care about ctty distinctions, it's just a descriptor.
    tty driver:
	remembers the process group associated with the tty - this is
	Sys5:	the shell & all its' children
	BSD:	the foreground job[s]; this is the shell or the current cmd.
    u area: 
	u_ttyp, u_ttyd, and u_tty[iv]p.  These all contain information
	that is specific to the ctty.
	short	*u_ttyp	  a pointer to the process group field in the tty driver
	dev_t	u_ttyd	  device # of the ctty
	vnode	*u_ttyvp  vnode of the ctty 

So, how do you know if there's a ctty for a process?  If u_ttyd != 0 then
this process has a ctty.  This is how /dev/tty works, by the way, it looks
at u_tty* to find out what /dev/tty really is.

Controlling tty's are used for signal handling.  The tty driver needs to know
who gets signaled when control chars come in; this is why it remembers 
the (foreground) process group of the tty.

Let's consider each of the methods outlined above, what they do, and how 
effective they are:

1.  setpgrp(2)

    System5.3 & earlier:
	This will release your controlling tty (zero u_tty*),
	place you in a new process group (pgrp = pid), and
	(maybe) note that you are a process group leader.

	Any references to the tty in the open file table are fine,
	you've still got them.  You no longer are vulnerable to signals
	from that tty.  Also /dev/tty access no longer works.

	This is the ``right way'' to do it under System5.3 and similar
	systems.
    
    BSD:
	This will only set p_pgrp.  It does nothing about your ctty (u_tty*
	are untouched).  It is a way of insulating yourself from signals from 
	the ctty but the ctty is still there, the ttys' pgrp is still in the
	tty driver, etc.

	This is not an acceptable method under BSD, it leaves things messy.

2.  ioctl(...TIOCSPGRP...)
	
    BSD only:
	This sets the tty drivers idea of who is the foreground process group.
	This is intended for use by the controlling shell in a job control
	environment, it is not a method of relinquishing your ctty.

3.  ioctl(...TIOCNOTTY...)

    BSD only:
	This identical to a sys5 setpgrp() with the following difference:
	p_pgrp is set to 0 instead of p_pid.  It's the best way to get rid
	of a ctty under BSD4.3 and earlier systems.

4.  closing all terminal files

	This closes the entry in the open file table; it does not do anything
	with the ctty information.  I wasn't sure about this so I tried it
	out: this prints out the hello:

	    #include <sys/file.h>
	    main() { int i;
		    close(0); close(1); close(2);
		    i = open("/dev/tty", O_RDWR);
		    if (i != -1) write(i, "Hello\n", 6);
	    }

===============================================================================

Something else to consider is how open() knows to hand out ctty's.  Again,
it depends on your environment:

    BSD4.3 and earlier:
	On these systems you get a ctty on open when your p_pgrp was 0.  It
	had the side effect of giving you a process group as well; if the tty
	had no process group, you got your pid as a pgrp, otherwise you 
	``joined'' the ttys' process group.  I believe that this joining
	business is a security hole.

    System5.3 & earlier:
	You needed to be a process group leader, not already have a ctty,
	and the tty cannot already have a process group.  The first two
	conditions would be satisfied if you had just done a setpgrp().
	The third was a security mechanism intended to make sure that
	the tty was not being shared illegally.  System5 is more secure than
	BSD in this respect.

    POSIX (4.4BSD, Sys5.4, SunOS4.1)
	You need to be a session leader, not have a ctty, and the tty cannot
	belong to any other session.  The first two would be satisfied if you
	had just done a setsid().  The last is a security measure a la Sys5.

Another note about how this changes under POSIX:  controlling tty's are really
a per session concept - this business about each process knowing about its'
ctty in the u area is wrong.  POSIX has a session concept and it is 
implemented (in SunOS4.1) as a struct; the proc struct points to it.  So you
end up with:

	OLD				NEW
	---				---
	u_ttyd				u_procp->p_sessp->s_ttyd
	u_ttyp				u_procp->p_sessp->s_ttyp
	u_ttyvp				u_procp->p_sessp->s_ttyvp

All processes in the same session point to the same session struct.  Note that
ttyd is really ttyvp->v_rdev; it's been kept because a lot of code assumes
that u_ttyd == 0 means no ctty; in our case u_ttyvp (aka s_ttyvp) == NULL
means no ctty.

If there's anyone out there that can point out mistakes I've made I'd like
to hear about it.  Or if you have comments, questions, clarifications,
send me mail.

I hope this helps.



More information about the Comp.unix.wizards mailing list