2

If I make the bash as the first process invoked (i.e as init), as a result it will display the following:

init: cannot set terminal process group (-1): Inappropriate ioctl for device
init: no job control in this shell

And no signals (e.g ^C, ^Z) work.

Through reading the source code of bash-5.1.12, the problem is located at the expression in job.c line 4501:

(t = tcgetpgrp (shell_tty)) == -1

The error value is ENOTTY, which mean that the calling process does not have a controlling terminal.

Why is Bash without a controlling terminal when invoked as init?

Li-Guangda
  • 257
  • 2
  • 3
  • 11
  • 2
    Note: init is very special (kernel and unix point of view). bash was modified to be able to run as init. signals to PID=1 are treated differently (by the kernel), and nobody should suspend init (so no job control, e.g. suspend init because it want to write to stdout). Bash was modified to run as init, but just for emergency situation, you will be outside POSIX and Unix behavious. It is just complex and probably unexpected interaction bewteen controlling terminal and init behavious – Giacomo Catenazzi Feb 17 '22 at 09:54
  • 1
    When `login` invoke `bash`, it will `setsid` before, but if invoke bash as init directly, its sid would be default 0. – Li-Guangda Mar 03 '22 at 01:43
  • The [follow-up](https://unix.stackexchange.com/questions/693541/would-the-process-running-as-pid-1-ignore-job-controll-signals-by-default?noredirect=1&lq=1) of this question. :) – Li-Guangda Mar 09 '22 at 12:51

1 Answers1

1

The controlling terminal of bash is prepared (opened) by login. So if bash is directly invoked as init skiping login, it will throw this issue above because the controlling-terminal plays a special role in handling the signals.

As per the source code login.c (in util-linux), the process of invoking bash in login roughly as following:

  1. main() invoke fork_session()

    /*
     * Detach the controlling terminal, fork, and create a new session
     * and reinitialize syslog stuff.
     */
    fork_session(&cxt);
    
  2. Fork a new process. (in fork_session() function)

    child_pid = fork();
    
  3. Make the new process as session leader. For acquiring a controlling terminal, the process must become a session leader before. (in fork_session() function)

    /* start new session */
    setsid();
    
  4. Acquire a controlling tty for the session leader. Session leader acquires a controlling terminal by open a tty. (in fork_session() function)

    /* make sure we have a controlling tty */
    open_tty(cxt->tty_path);
    openlog("login", LOG_ODELAY, LOG_AUTHPRIV); /* reopen */
    
    /*
     * TIOCSCTTY: steal tty from other process group.
     */
    if (ioctl(0, TIOCSCTTY, 1))
        syslog(LOG_ERR, _("TIOCSCTTY failed: %m"));
    
  5. Exec a login shell in the new process. (return in main())

    /* if the shell field has a space: treat it like a shell script */
    if (strchr(pwd->pw_shell, ' ')) {
       char *buff;
    
       xasprintf(&buff, "exec %s", pwd->pw_shell);
       child_argv[child_argc++] = "/bin/sh";
       child_argv[child_argc++] = "-sh";
       child_argv[child_argc++] = "-c";
       child_argv[child_argc++] = buff;
    } else {
       char tbuf[PATH_MAX + 2], *p;
    
       tbuf[0] = '-';
       xstrncpy(tbuf + 1, ((p = strrchr(pwd->pw_shell, '/')) ?
                   p + 1 : pwd->pw_shell), sizeof(tbuf) - 1);
    
       child_argv[child_argc++] = pwd->pw_shell;
       child_argv[child_argc++] = xstrdup(tbuf);
    }
    
    child_argv[child_argc++] = NULL;
    
    /* http://www.linux-pam.org/Linux-PAM-html/adg-interface-by-app-expected.html#adg-pam_end */
    (void) pam_end(cxt.pamh, PAM_SUCCESS|PAM_DATA_SILENT);
    
    execvp(child_argv[0], child_argv + 1);
    

More of links about the concept of controlling terminal:

Li-Guangda
  • 257
  • 2
  • 3
  • 11