Linux
Application
Development

Michael K. Johnson
Erik W. Troan

passfd.c

/* passfd.c -- sample program which passes a file descriptor */

/* We behave like a simple /bin/cat, which only handles one 
   argument (a file name). We create Unix domain sockets through
   socketpair(), and then fork(). The child opens the file whose 
   name is passed on the command line, passes the file descriptor 
   and file name back to the parent, and then exits. The parent waits 
   for the file descriptor from the child, then copies data from that 
   file descriptor to stdout until no data is left. The parent then 
   exits. */

#include <alloca.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <unistd.h>

#include "sockutil.h"		/* simple utility functions */

/* The child process. This sends the file descriptor. */
int childProcess(char * filename, int sock) {
    int fd;
    struct iovec vector;        /* some data to pass w/ the fd */
    struct msghdr msg;          /* the complete message */
    struct cmsghdr * cmsg;      /* the control message, which will */
                                /* include the fd */

    /* Open the file whose descriptor will be passed. */
    if ((fd = open(filename, O_RDONLY)) < 0) {
        perror("open");
        return 1;
    }

    /* Send the file name down the socket, including the trailing
       '\0' */
    vector.iov_base = filename;
    vector.iov_len = strlen(filename) + 1;

    /* Put together the first part of the message. Include the
       file name iovec */
    msg.msg_name = NULL;
    msg.msg_namelen = 0;
    msg.msg_iov = &vector;
    msg.msg_iovlen = 1;

    /* Now for the control message. We have to allocate room for
       the file descriptor. */
    cmsg = alloca(sizeof(struct cmsghdr) + sizeof(fd));
    cmsg->cmsg_len = sizeof(struct cmsghdr) + sizeof(fd);
    cmsg->cmsg_level = SOL_SOCKET;
    cmsg->cmsg_type = SCM_RIGHTS;
  
    /* copy the file descriptor onto the end of the control 
       message */
    memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd));

    msg.msg_control = cmsg;
    msg.msg_controllen = cmsg->cmsg_len;

    if (sendmsg(sock, &msg, 0) != vector.iov_len)
        die("sendmsg");

    return 0;
}

/* The parent process. This receives the file descriptor. */
int parentProcess(int sock) {
    char buf[80];               /* space to read file name into */
    struct iovec vector;	/* file name from the child */
    struct msghdr msg;		/* full message */
    struct cmsghdr * cmsg;      /* control message with the fd */
    int fd;

    /* set up the iovec for the file name */
    vector.iov_base = buf;
    vector.iov_len = 80;

    /* the message we're expecting to receive */

    msg.msg_name = NULL;
    msg.msg_namelen = 0;
    msg.msg_iov = &vector;
    msg.msg_iovlen = 1;

    /* dynamically allocate so we can leave room for the file
       descriptor */
    cmsg = alloca(sizeof(struct cmsghdr) + sizeof(fd));
    cmsg->cmsg_len = sizeof(struct cmsghdr) + sizeof(fd);
    msg.msg_control = cmsg;
    msg.msg_controllen = cmsg->cmsg_len;

    if (!recvmsg(sock, &msg, 0)) 
        return 1;

    printf("got file descriptor for '%s'\n", 
           (char *) vector.iov_base);

    /* grab the file descriptor from the control structure */
    memcpy(&fd, CMSG_DATA(cmsg), sizeof(fd));
   
    copyData(fd, 1);

    return 0;
}

int main(int argc, char ** argv) {
    int socks[2];
    int status;

    if (argc != 2) {
        fprintf(stderr, "only a single argument is supported\n");
        return 1;
    }

    /* Create the sockets. The first is for the parent and the
       second is for the child (though we could reverse that
       if we liked. */
    if (socketpair(PF_UNIX, SOCK_STREAM, 0, socks)) 
        die("socketpair");

    if (!fork()) {
	/* child */
        close(socks[0]);
        return childProcess(argv[1], socks[1]);
    }

    /* parent */
    close(socks[1]);
    parentProcess(socks[0]);

    /* reap the child */
    wait(&status);

    if (WEXITSTATUS(status))
        fprintf(stderr, "child failed\n");

    return 0;
}