Cristiano Paris
Cristiano Paris

Reputation: 1856

Detaching terminal of a parent process

I'm experimenting with namespaces under Linux so I'm writing a small C program to isolate a Debian Wheezy environment created with debootstrap.

I could successfully start sysv-init and get a login prompt but when I close the isolated environment, either shutting down the system or killing -9 init, the terminal is left in a state where no controlling terminal seems to be attached to the shell. Specifically, if I start sudo it complains that no terminal is present.

I narrowed down the failure point in sudo to the following statement:

open("/dev/tty", O_RDWR|O_NOCTTY);

with error ENXIO (i.e. "No such device or address").

I'm trying to understand why this happens and I have the feeling that's something related to the setsid() system call in init but I couldn't reproduce the exact scenario so I'm unable to provide a proper test case.

What seems really odd to me is that, not only does init (a forked process and thus a child to the shell) detach from the current terminal but also all the hierarchy of processess up to the GUI terminal get detached from the tty as well, which I can't seem to figure out how it happens.

In addition, there is some inconsistencies between different commands:

➜  namespaces  tty
/dev/pts/19
➜  namespaces  sudo -s
sudo: no tty present and no askpass program specified
➜  namespaces  ls -l /proc/$$/fd
total 0
lrwx------ 1 paris paris 64 gen 15 23:24 0 -> /dev/pts/19
lrwx------ 1 paris paris 64 gen 15 23:24 1 -> /dev/pts/19
lrwx------ 1 paris paris 64 gen 15 23:24 10 -> /dev/pts/19
lrwx------ 1 paris paris 64 gen 15 23:24 2 -> /dev/pts/19
➜  namespaces  

Any clue about this situation is hihgly appreciated.

EDIT: Looking at the kernel source code for "/dev/tty" I think the problem is related to the ref counting of the char device on the kernel side. Indeed, in order to redirect "/dev/{console|tty0|tty1}" output to the current pty I bind mounted the controlling terminal of the shell to those device file in the container's mounted dev.

EDIT: It seems that in the Linux kernel the error is reported in this step in the "tty_open_current_tty()" function:

static struct tty_struct *tty_open_current_tty(dev_t device, struct file *filp)
{
     struct tty_struct *tty;
     int retval;

     if (device != MKDEV(TTYAUX_MAJOR, 0))
         return NULL;

     tty = get_current_tty();
     if (!tty)
         return ERR_PTR(-ENXIO);
     ...
}

EDIT: It seems that the problem is related to the concept of "stealing" a controlling tty. This can be done under the CAP_SYS_ADMIN capability and calling ioctl() on the tty file descriptor using TIOCSCTTY as the command and 1 as the parameter (see tty_ioctl(4)). I'll try to write a test case to confirm this and report back.

Upvotes: 1

Views: 1439

Answers (2)

Cristiano Paris
Cristiano Paris

Reputation: 1856

Okay, I could successfully track down the problem.

See this gist for a working code example (replace /dev/pts/17 with the output of the tty command).

The problem is related to the following step in sysvinit's spawn() function:

(void)ioctl(f, TIOCSCTTY, 1);

ioctl() is actually stealing the controlling tty /dev/console which, in my case, is a bind mount of the pty of the current process.

Upvotes: 1

mauzel
mauzel

Reputation: 1616

I think your hunch about setsid() is probably close. Are there fork() invocations close to the setsid() invocation? Because a common technique for turning a process into a daemon is to:

  • fork()
  • setsid()
  • fork() again to make sure everything is detached

Edit

The source code for the function in question (init_main): http://svn.savannah.nongnu.org/viewvc/sysvinit/trunk/src/init.c?root=sysvinit&view=markup

It has the general fork, setsid, fork pattern repeated multiple times. This will ensure detachment from the tty.

Upvotes: 1

Related Questions