This is widely considered the “killer feature” of Unix Domain Sockets. It allows one process to open a file (or socket) and then “pass” that open connection to a completely different process.

1. The Concept: What really happens?

When you pass a file descriptor, you are not just passing the integer number (like 4 or 5). You are passing the kernel’s reference to the open file.

  • Process A might call the descriptor 4.
  • Process B receives it, and the system might assign it the number 6.
  • Crucial Point: Both 4 (in Process A) and 6 (in Process B) point to the exact same entry in the kernel’s file table. They share the file offset and status flags.

Real-World Metaphor: Imagine Process A has a key to a specific safety deposit box. Process A cannot just shout the number on the key (“Key #45”) to Process B, because Process B’s key #45 might open a gym locker. Instead, Process A uses a secure courier (Unix Domain Socket) to physically copy the key and hand the copy to Process B. Now Process B has its own key that opens the same box.

The mycat program after creating stream pipe using socketpair A bi-directional “stream pipe” connects these two descriptors. Data written to [0] appears at [1], and vice versa.

This diagram shows the system state after mycat has forked a child process and executed the openfile program. The diagram illustrates the goal: The Child opens a file (e.g., /etc/passwd), getting a new descriptor (let’s say fd 4). It then sends fd 4 through the stream pipe (sockfd[1]) to the Parent. The Parent receives it as a new descriptor (e.g., fd 5) that points to the exact same file table entry as the Child’s fd 4.

2. How it works: SCM_RIGHTS

You cannot use standard write() or send() to pass descriptors. You must use sendmsg() and recvmsg() because descriptors are sent as Ancillary Data (also called Control Information).

  • Socket Family: Must be AF_LOCAL (Unix Domain).
  • Control Message Type: SCM_RIGHTS (stands for Socket Control Message Rights).
3. The Code Implementation

The book provides two helper functions, write_fd and read_fd, to handle the messy details of the msghdr and cmsghdr structures. Sending a Descriptor (write_fd) To send a descriptor, we must package it inside a cmsghdr structure.

ssize_t
write_fd(int fd, void *ptr, size_t nbytes, int sendfd)
{
    struct msghdr   msg;
    struct iovec    iov[1];
    union {
      struct cmsghdr    cm;
      char              control[CMSG_SPACE(sizeof(int))];
    } control_un;
    struct cmsghdr  *cmptr;
 
    // 1. Setup the data buffer (we usually send at least 1 byte of real data)
    msg.msg_control = control_un.control;
    msg.msg_controllen = sizeof(control_un.control);
 
    // 2. Setup the Ancillary Data
    cmptr = CMSG_FIRSTHDR(&msg);
    cmptr->cmsg_len = CMSG_LEN(sizeof(int));
    cmptr->cmsg_level = SOL_SOCKET;
    cmptr->cmsg_type = SCM_RIGHTS;  // <--- The magic flag
    
    // 3. Put the descriptor into the data area
    *((int *) CMSG_DATA(cmptr)) = sendfd; 
 
    // 4. Setup normal data (pointers, lengths, etc.)
    msg.msg_name = NULL;
    msg.msg_namelen = 0;
    iov[0].iov_base = ptr;
    iov[0].iov_len = nbytes;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;
 
    return(sendmsg(fd, &msg, 0));
}

Receiving a Descriptor (read_fd) Receiving is the reverse. We look for the SCM_RIGHTS flag in the received control data.

ssize_t
read_fd(int fd, void *ptr, size_t nbytes, int *recvfd)
{
    struct msghdr   msg;
    struct iovec    iov[1];
    struct cmsghdr  *cmptr;
    // ... (union definition same as above) ...
 
    // Setup buffers ...
    msg.msg_control = control_un.control;
    msg.msg_controllen = sizeof(control_un.control);
    
    // Perform the read
    n = recvmsg(fd, &msg, 0);
 
    // Check if we received a descriptor
    if ( (cmptr = CMSG_FIRSTHDR(&msg)) != NULL &&
         cmptr->cmsg_len == CMSG_LEN(sizeof(int))) {
        if (cmptr->cmsg_level != SOL_SOCKET)
            err_quit("control level != SOL_SOCKET");
        if (cmptr->cmsg_type != SCM_RIGHTS)
            err_quit("control type != SCM_RIGHTS");
            
        // Extract the descriptor
        *recvfd = *((int *) CMSG_DATA(cmptr)); 
    } else {
        *recvfd = -1; // No descriptor was sent
    }
 
    return(n);
}
4. Why send 1 byte of data?

You will notice write_fd sends normal data (ptr, nbytes) along with the descriptor. Some implementations typically require at least 1 byte of real data to be sent along with the ancillary data, or the call might fail or not wake up the receiver correctly. It serves as a carrier for the descriptor.

This mechanism is how the inetd “superserver” works on some systems, or how advanced web servers (like Nginx) pass connections from a privileged parent process to unprivileged worker processes.

Does the distinction between the “file descriptor integer” and the “open file description” make sense? If so, we can briefly look at Section 15.8 (Receiving Sender Credentials) or wrap up the chapter.