/*
 * Send a file descriptor through a Unix domain socket
 *
 * Unfortunately, this program is probably not exactly 100% portable.
 *
 * One strange wrinkle required by at least certain versions of Linux
 * is that you must also send a non-empty message (that's why the code
 * below sends the string "hello" in addition to the file descriptor).
 *
 * Author:  Daniel Boulet (danny@obtuse.com)
 *
 * Copyright 1997, 1999 Daniel Boulet
 * All rights reserved
 *
 * Permission is granted to use this software in any way you like as long
 * as this entire comment is included in the source code.
 *
 * THIS SOFTWARE IS PROVIDED WITHOUT WARRANTEE OF ANY KIND WHAT-SO-EVER.
 */

/*
 * Portability experience:
 *
 * OS			Experience
 * --------------------------------------------------------------------
 * BSD/OS		seems to work as-is
 * Linux kernel 2.2	seems to work as-is
 * AIX 4.3		seems to work as-is
 * OpenBSD		seems to work as-is
 * Solaris 2.6		requires a few changes:
 *			 - msg_control renamed as msg_accrights
 *			 - msg_controllen renamed as msg_accrightslen
 *			 - set msg_accrights to point at int containing fd
 *			 - set msg_accrights to sizeof(int)
 *
 * Please let me know your experiences on other Unix variants.
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <stdio.h>

main()
{
    int pair[2];
    struct msghdr mh;
    struct cmsghdr cmh[2];
    struct iovec iov;
    char tbuf[100];
    int fd;
    int rlen;
    pid_t pid;
    char buffer[1024];

    if ( socketpair(AF_UNIX, SOCK_STREAM, 0, pair) < 0 ) {
	perror("socketpair");
	exit(1);
    }

    fflush(stdout);
    fflush(stderr);

    switch ( pid = fork() ) {

    case 0:		/* Child sends the file descriptor */
	fd = open("sendfd.c",0);
	if ( fd < 0 ) {
	    perror("open");
	    exit(0);
	}
	memset(&mh,0,sizeof(mh));
	mh.msg_name = 0;
	mh.msg_namelen = 0;
	mh.msg_iov = &iov;
	mh.msg_iovlen = 1;
	mh.msg_control = (caddr_t)&cmh[0];
	mh.msg_controllen = sizeof(cmh[0]) + sizeof(int);
	mh.msg_flags = 0;
	iov.iov_base = "hello";
	iov.iov_len = strlen(iov.iov_base) + 1;
	cmh[0].cmsg_level = SOL_SOCKET;
	cmh[0].cmsg_type = SCM_RIGHTS;
	cmh[0].cmsg_len = sizeof(cmh[0]) + sizeof(int);
	*(int *)&cmh[1] = fd;
	if ( sendmsg(pair[1],&mh,0) < 0 ) {
	    perror("sendmsg");
	    exit(1);
	}
	close(fd);
	printf("child done\n");
	fflush(stdout);
	exit(0);

    case -1:		/* Oops! */
	perror("fork");
	exit(1);

    default:		/* Parent receives the file descriptor */
	mh.msg_name = 0;
	mh.msg_namelen = 0;
	mh.msg_iov = &iov;
	mh.msg_iovlen = 1;
	mh.msg_control = (caddr_t)&cmh[0];
	mh.msg_controllen = sizeof(cmh[0]) * 2;
	iov.iov_base = tbuf;
	iov.iov_len = sizeof(tbuf);
	cmh[0].cmsg_len = sizeof(cmh[0]) + sizeof(int);
	if ( (rlen = recvmsg(pair[0],&mh,0)) < 0 ) {
	    perror("recvmsg");
	    exit(1);
	}
	fd = *(int *)&cmh[1];
	printf("rlen = %d msg_controllen = %d, cmsg_len = %d\n",
	    rlen,mh.msg_controllen,cmh[0].cmsg_len);
	printf("cmsg_level = %d, cmsg_type = %d, fd = %d\n",
	    cmh[0].cmsg_level,cmh[0].cmsg_type,fd);
	while ( (rlen = read(fd,buffer,sizeof(buffer))) > 0 ) {
	    write(1,buffer,rlen);
	}
	if ( rlen < 0 ) {
	    perror("read");
	    exit(1);
	}
	exit(0);
    }
}

