Reputation: 1856
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
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
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 detachedEdit
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