Controlling Terminals and Controlling Processes

 

34.4 — Controlling Terminals and Controlling Processes
Chapter 34 · The Linux Programming Interface · EmbeddedPathashala
/dev/tty, O_NOCTTY, TIOCNOTTY, TIOCSCTTY, ctermid()
Key Terms in This Section

Controlling Terminal Controlling Process /dev/tty O_NOCTTY TIOCNOTTY TIOCSCTTY tcgetsid() ctermid() ENXIO SIGHUP on disconnect

How Does a Session Get a Controlling Terminal?

When a new session is created with setsid(), it has no controlling terminal. A controlling terminal is acquired later when the session leader opens a terminal device for the first time.

Under Linux (System V behaviour):

  • The session leader opens a terminal file (e.g., /dev/pts/1).
  • If that terminal is not already the controlling terminal for another session, it becomes the controlling terminal for this session.
  • If the session leader specifies O_NOCTTY when calling open(), the terminal is not made the controlling terminal.
  • A terminal can only be the controlling terminal for one session at a time.
BSD vs System V behaviour: On BSD systems, opening a terminal never automatically makes it the controlling terminal. The session leader must explicitly call ioctl(fd, TIOCSCTTY) to set the controlling terminal. Linux supports both — the automatic (System V) way and the explicit ioctl way.

The Controlling Process

As a consequence of opening the controlling terminal, the session leader simultaneously becomes the controlling process for that terminal.

The controlling process is special for one reason: if a terminal disconnect occurs (modem hangup, terminal window closed), the kernel sends SIGHUP to the controlling process to inform it. The default action of SIGHUP is to terminate the process.

The controlling terminal and controlling process are inherited across fork() and preserved across exec().

Accessing the Controlling Terminal: /dev/tty

/dev/tty is a special device file that always refers to the controlling terminal of the calling process. It is useful when standard input/output is redirected and you need to ensure you are talking directly to the terminal.

Example: getpass() (the function that reads a password without echoing) opens /dev/tty to ensure it always reads from the terminal, even if stdin is redirected.

If the process has no controlling terminal, opening /dev/tty fails with errno == ENXIO.

Removing the Controlling Terminal: TIOCNOTTY

A process can detach itself from its controlling terminal using:

ioctl(fd, TIOCNOTTY);

where fd is a file descriptor for the controlling terminal. After this call, opening /dev/tty will fail.

If the calling process is the controlling process, three additional things happen:

  1. All processes in the session lose their association with the controlling terminal.
  2. The terminal becomes free — another session leader can acquire it.
  3. SIGHUP (and SIGCONT) are sent to all members of the foreground process group.

Getting the Controlling Terminal Path: ctermid()

ctermid() returns a string containing the pathname of the controlling terminal. On Linux this is always /dev/tty, but the function exists for portability to non-UNIX systems.

#include <stdio.h>

/* If ttyname is not NULL: must be at least L_ctermid bytes; path is copied there.
 * If ttyname is NULL: returns pointer to a static buffer (not reentrant). */
char *ctermid(char *ttyname);
Mechanism What it does Notes
open(terminal, 0) Automatically acquires controlling terminal System V; Linux default
open(terminal, O_NOCTTY) Opens terminal WITHOUT making it controlling POSIX standard flag
ioctl(fd, TIOCSCTTY) Explicitly sets controlling terminal BSD style; also on Linux
ioctl(fd, TIOCNOTTY) Removes controlling terminal association Not in SUSv3 but widespread
tcgetsid(fd) Gets session ID of terminal’s session SUSv3; uses TIOCGSID ioctl
ctermid() Returns pathname of controlling terminal Always “/dev/tty” on Linux

Code Example 1 — Opening /dev/tty to Directly Access the Terminal

/* open_dev_tty.c
 * Shows how to open /dev/tty to communicate with the terminal
 * even when stdin/stdout are redirected.
 * Compile: gcc -o open_dev_tty open_dev_tty.c
 * Test redirect: ./open_dev_tty < /dev/null > /dev/null
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

int main(void)
{
    /* stdin and stdout might be redirected — but /dev/tty is always
     * the controlling terminal */
    int tty_fd = open("/dev/tty", O_RDWR);

    if (tty_fd == -1) {
        fprintf(stderr, "open /dev/tty failed: %s\n", strerror(errno));
        if (errno == ENXIO)
            fprintf(stderr, "  → Process has NO controlling terminal\n");
        return 1;
    }

    /* Write a message directly to the terminal */
    const char *msg = "Hello from /dev/tty! (bypasses stdout redirection)\n";
    write(tty_fd, msg, strlen(msg));

    /* Read a line directly from the terminal */
    write(tty_fd, "Enter your name: ", 17);
    char buf[64] = {0};
    ssize_t n = read(tty_fd, buf, sizeof(buf) - 1);
    if (n > 0) {
        buf[n] = '\0';
        /* Remove newline */
        if (buf[n-1] == '\n') buf[n-1] = '\0';
        char reply[128];
        snprintf(reply, sizeof(reply), "Hello, %s!\n", buf);
        write(tty_fd, reply, strlen(reply));
    }

    close(tty_fd);
    return 0;
}
/* Run with redirected stdin/stdout:
 * ./open_dev_tty < /dev/null > /dev/null
 * The message and prompt still appear on the terminal!
 */

Code Example 2 — O_NOCTTY and tcgetsid()

/* noctty_demo.c
 * Demonstrates O_NOCTTY flag — opening a terminal without acquiring
 * it as the controlling terminal.
 * Compile: gcc -o noctty_demo noctty_demo.c
 */
#define _XOPEN_SOURCE 500
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <string.h>
#include <errno.h>

int main(void)
{
    printf("My SID = %ld\n", (long)getsid(0));
    printf("My PGID = %ld\n\n", (long)getpgrp());

    /* --- Open a terminal WITH O_NOCTTY --- */
    /* On a typical system, /dev/tty is the current controlling terminal;
     * to test with a different terminal, replace with e.g. /dev/pts/2 */
    int fd = open("/dev/tty", O_RDWR | O_NOCTTY);
    if (fd == -1) {
        fprintf(stderr, "open /dev/tty: %s\n", strerror(errno));
        return 1;
    }
    printf("Opened /dev/tty with O_NOCTTY\n");

    /* tcgetsid: returns session ID associated with the terminal */
    pid_t term_sid = tcgetsid(fd);
    if (term_sid == -1) {
        printf("tcgetsid: %s\n", strerror(errno));
    } else {
        printf("Terminal's session ID: %ld\n", (long)term_sid);
        printf("Our session ID      : %ld\n", (long)getsid(0));
        printf("Same session? %s\n",
               (term_sid == getsid(0)) ? "YES" : "NO");
    }

    /* ctermid: get pathname of controlling terminal */
    char ttyname[L_ctermid];
    if (ctermid(ttyname) != NULL)
        printf("ctermid() says controlling terminal is: %s\n", ttyname);
    else
        printf("ctermid() could not determine controlling terminal\n");

    close(fd);
    return 0;
}
/*
 * Expected output:
 * My SID = 1200
 * My PGID = 5000
 *
 * Opened /dev/tty with O_NOCTTY
 * Terminal's session ID: 1200
 * Our session ID      : 1200
 * Same session? YES
 * ctermid() says controlling terminal is: /dev/tty
 */

Interview Questions — Section 34.4

Q1. What is /dev/tty and why is it useful?
/dev/tty is a special device file that always refers to the calling process’s controlling terminal. It is useful when stdin/stdout are redirected and a program needs to communicate directly with the terminal — for example, to prompt for a password. If the process has no controlling terminal, opening /dev/tty fails with errno ENXIO.
Q2. What does the O_NOCTTY flag do when passed to open()?
O_NOCTTY tells the kernel not to make the opened terminal the controlling terminal for the session, even if the caller is the session leader and the terminal is not yet claimed. This is useful when a program needs to open a terminal device for data I/O without accidentally acquiring it as a controlling terminal.
Q3. What is the difference between System V and BSD behaviour for acquiring a controlling terminal?
On System V (and Linux by default), when a session leader opens a terminal device without O_NOCTTY, the terminal automatically becomes the controlling terminal. On BSD, this never happens automatically — the session leader must explicitly call ioctl(fd, TIOCSCTTY) to set the controlling terminal. Linux supports both methods.
Q4. What happens when ioctl(fd, TIOCNOTTY) is called by the controlling process?
Three things happen: (1) All processes in the session lose their controlling terminal association. (2) The terminal becomes free and can be acquired by another session. (3) SIGHUP and SIGCONT are sent to all members of the foreground process group to notify them of the loss of the controlling terminal.
Q5. How is the controlling terminal inherited by child processes?
The controlling terminal is inherited by the child from its parent across fork(). It is also preserved across exec() calls. So a child process and all its descendants share the same controlling terminal as the session leader, unless they explicitly detach (via TIOCNOTTY or by creating a new session with setsid()).

Leave a Reply

Your email address will not be published. Required fields are marked *