Sessions – linux system programming

 

34.3 — Sessions
Chapter 34 · The Linux Programming Interface · EmbeddedPathashala
Creating sessions with setsid(), the daemon pattern, and why group leaders cannot call setsid()
Key Terms in This Section

getsid() setsid() Session Leader New Session No Controlling Terminal Process Group Leader restriction Daemon creation pattern fork() before setsid() /dev/tty ENXIO

Getting the Session ID: getsid()

getsid(pid) returns the session ID of the specified process. Pass 0 to get the session ID of the calling process.

The session ID equals the PID of the session leader. A new process inherits its parent’s session ID.

#define _XOPEN_SOURCE 500
#include <unistd.h>

pid_t getsid(pid_t pid);
/* pid == 0  → session ID of calling process
 * Returns session ID on success, -1 on error (EPERM on some systems) */
Note: On some UNIX implementations (not Linux), getsid() returns EPERM if the target process is in a different session. On Linux this restriction does not apply — you can always get the SID of any process.

Creating a New Session: setsid()

setsid() creates a brand new session. It is the key call for creating daemon processes.

When a non-group-leader process calls setsid(), three things happen simultaneously:

1 The calling process becomes the leader of a new session. Its SID is set to its own PID.
2 It also becomes the leader of a new process group within that session. Its PGID is set to its own PID.
3 The new session has no controlling terminal. Any previous connection to a terminal is severed.
#include <unistd.h>

pid_t setsid(void);
/* Returns: new session ID on success, -1 on error (EPERM if caller is a group leader) */

Why a Process Group Leader Cannot Call setsid()

If the calling process is already a process group leader, setsid() fails with EPERM. This is a deliberate restriction.

If a group leader were allowed to call setsid(), it would create a new session while the other members of its process group remained in the old session. This would break the rule that all members of a process group must be in the same session.

Solution: Always fork() first, have the parent exit, then have the child call setsid(). The child gets a new unique PID which is different from any existing PGID, so it cannot be a group leader.

Daemon creation pattern (fork + setsid):
The call to setsid() is one of the first steps in making a daemon process. A daemon must have no controlling terminal so it does not receive terminal signals, and setsid() achieves that.

Code Example 1 — Creating a New Session (t_setsid)

/* t_setsid.c
 * Demonstrates setsid() to create a new session.
 * Verifies the new session has no controlling terminal by trying to open /dev/tty.
 * Compile: gcc -o t_setsid t_setsid.c
 */
#define _XOPEN_SOURCE 500
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

int main(void)
{
    printf("Before fork:\n");
    printf("  PID=%ld  PGID=%ld  SID=%ld\n",
           (long)getpid(), (long)getpgrp(), (long)getsid(0));

    /* Step 1: fork so child is NOT a process group leader */
    pid_t pid = fork();
    if (pid == -1) { perror("fork"); exit(EXIT_FAILURE); }

    if (pid != 0) {
        /* Parent exits — child is now an orphan (adopted by init) */
        printf("Parent exiting...\n");
        exit(EXIT_SUCCESS);
    }

    /* --- CHILD CONTINUES --- */
    printf("\nChild (PID=%ld) calling setsid()...\n", (long)getpid());

    /* Step 2: child calls setsid() — cannot be group leader here */
    pid_t sid = setsid();
    if (sid == -1) {
        perror("setsid");
        exit(EXIT_FAILURE);
    }

    printf("After setsid():\n");
    printf("  PID=%ld  PGID=%ld  SID=%ld\n",
           (long)getpid(), (long)getpgrp(), (long)getsid(0));
    /* Note: PID == PGID == SID — all equal after setsid() */

    /* Step 3: Verify no controlling terminal by opening /dev/tty */
    int fd = open("/dev/tty", O_RDWR);
    if (fd == -1) {
        /* ENXIO means no controlling terminal — expected! */
        printf("open(\"/dev/tty\") failed: %s (errno=%d)\n",
               strerror(errno), errno);
        printf("  → Confirmed: new session has NO controlling terminal\n");
    } else {
        printf("open(\"/dev/tty\") succeeded (unexpected)\n");
        close(fd);
    }

    return 0;
}
/*
 * Expected output:
 * Before fork:
 *   PID=12352  PGID=12352  SID=12243    ← shell's PID is SID
 * Parent exiting...
 * Child (PID=12353) calling setsid()...
 * After setsid():
 *   PID=12353  PGID=12353  SID=12353    ← PID == PGID == SID
 * open("/dev/tty") failed: No such device or address (errno=6)
 *   → Confirmed: new session has NO controlling terminal
 */

Code Example 2 — Verifying setsid() Fails for Group Leader

/* setsid_group_leader_fail.c
 * Demonstrates that setsid() fails with EPERM if the caller is a process group leader.
 * Compile: gcc -o sgl setsid_group_leader_fail.c
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

int main(void)
{
    printf("=== Testing setsid() restrictions ===\n\n");

    /* Case 1: main process is typically already a group leader */
    printf("Case 1: Calling setsid() directly (process is group leader)\n");
    printf("  PID=%ld  PGID=%ld\n", (long)getpid(), (long)getpgrp());

    if (getpid() == getpgrp()) {
        printf("  We ARE the group leader — setsid() should fail.\n");
    }

    pid_t result = setsid();
    if (result == -1) {
        printf("  setsid() returned -1, errno=%d (%s)\n",
               errno, strerror(errno));  /* EPERM */
    } else {
        printf("  setsid() succeeded (we were not a group leader)\n");
    }

    printf("\nCase 2: fork() first, then setsid() in child\n");
    pid_t child = fork();
    if (child == -1) { perror("fork"); return 1; }

    if (child == 0) {
        /*
         * The child has a new PID which is guaranteed to differ from
         * any existing PGID, so the child is NOT a group leader.
         */
        printf("  Child PID=%ld  PGID=%ld\n",
               (long)getpid(), (long)getpgrp());
        printf("  Child is%s a group leader.\n",
               (getpid() == getpgrp()) ? "" : " NOT");

        pid_t sid = setsid();
        if (sid == -1) {
            perror("  child setsid");
        } else {
            printf("  child setsid() succeeded! SID=%ld\n", (long)sid);
        }
        return 0;
    }
    wait(NULL);
    return 0;
}
/*
 * Expected output:
 * Case 1: Calling setsid() directly (process is group leader)
 *   PID=5000  PGID=5000
 *   We ARE the group leader — setsid() should fail.
 *   setsid() returned -1, errno=1 (Operation not permitted)
 *
 * Case 2: fork() first, then setsid() in child
 *   Child PID=5001  PGID=5000
 *   Child is NOT a group leader.
 *   child setsid() succeeded! SID=5001
 */

Interview Questions — Section 34.3

Q1. What three things does setsid() do when called by a non-group-leader process?
First, the calling process becomes the leader of a new session — its SID is set to its own PID. Second, it becomes the leader of a new process group within that session — its PGID is also set to its own PID. Third, the new session has no controlling terminal — any previous terminal connection is severed.
Q2. Why can a process group leader not call setsid()?
Because if the group leader moved to a new session, the remaining members of its old group would still be in the old session, violating the rule that all members of a process group must be in the same session. The EPERM error prevents this inconsistency. The workaround is to fork() first and have the parent exit, leaving the child free to call setsid() since the child cannot be a group leader.
Q3. After a successful setsid() call, what are the values of PID, PGID, and SID?
All three are equal to the caller’s own PID. The process is simultaneously: its own PID, the leader of a new process group (PGID = PID), and the leader of a new session (SID = PID).
Q4. Why is setsid() important in daemon creation?
A daemon must run completely detached from any terminal. By calling setsid(), the daemon creates a new session with no controlling terminal, which means it cannot receive terminal-generated signals like SIGHUP, SIGINT, or SIGTSTP. This is one of the standard steps in daemonizing a process (along with fork(), closing file descriptors, chdir(“/”), etc.).
Q5. How can you check that a new session has no controlling terminal?
Try to open /dev/tty. This special file refers to the process’s controlling terminal. If the process has no controlling terminal, open(“/dev/tty”, …) fails with errno ENXIO (No such device or address). This is exactly how the t_setsid example in the book verifies the result.

Leave a Reply

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