#ifdef __sun
  #define _XOPEN_SOURCE 1
  #define _XOPEN_SOURCE_EXTENDED 1
  #define __EXTENSIONS__ 1
#endif

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include <stddef.h> // needed by broken bsds for NULL used in sys/uio.h
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/socket.h>

#ifndef CMSG_SPACE
# define CMSG_SPACE(len) (sizeof (struct cmsghdr) + len)
#endif

#ifndef CMSG_LEN
# define CMSG_LEN(len) (sizeof (struct cmsghdr) + len)
#endif

int
_fd_sendata (int socket, void * buf, int len, int fd)
{

  int size;
  struct msghdr msg;
  struct iovec iov;
  struct cmsghdr *cmsg;
  union {
	struct cmsghdr	cmsghdr;
	char		control[CMSG_SPACE(sizeof (int))];
  } cmsgu;

  iov.iov_base         = buf;
  iov.iov_len          = len;

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

  if (fd >= 0) {
    msg.msg_control    = cmsgu.control;
    msg.msg_controllen = sizeof(cmsgu.control);

    cmsg               = CMSG_FIRSTHDR(&msg);
    cmsg->cmsg_len     = CMSG_LEN(sizeof (int));
    cmsg->cmsg_level   = SOL_SOCKET;
    cmsg->cmsg_type    = SCM_RIGHTS;

 //printf ("passing fd %d\n", fd);
    *((int *) CMSG_DATA(cmsg)) = fd;
  } else {
    msg.msg_control    = NULL;
    msg.msg_controllen = 0;
 //printf ("not passing fd\n");
  }

  size = sendmsg(socket, &msg, 0);

  return size;
}

int
_fd_recvdata (int socket, void * buf, int len, int * fd)
{

  int size;

  if (fd) {
    struct msghdr msg;
    struct iovec iov;
    struct cmsghdr *cmsg;

    union {
	struct cmsghdr	cmsghdr;
	char		control[CMSG_SPACE(sizeof (int))];
    } cmsgu;

    iov.iov_base       = buf;
    iov.iov_len        = len;

    msg.msg_name       = NULL;
    msg.msg_namelen    = 0;
    msg.msg_iov        = &iov;
    msg.msg_iovlen     = 1;
    msg.msg_control    = cmsgu.control;
    msg.msg_controllen = sizeof(cmsgu.control);

    size               = recvmsg (socket, &msg, 0);

    if (size < 0) {
 //fprintf(stderr, "sock %d , size to small\n",socket);
 //perror(strerror(errno));
      return -1;
    }

    cmsg = CMSG_FIRSTHDR(&msg);
    if (cmsg && cmsg->cmsg_len == CMSG_LEN(sizeof(int))) {
      if (cmsg->cmsg_level != SOL_SOCKET) {
  //fprintf (stderr, "invalid cmsg_level %d\n", cmsg->cmsg_level);
	return -1;
      }
      else if (cmsg->cmsg_type != SCM_RIGHTS) {
 //fprintf (stderr, "invalid cmsg_type %d\n", cmsg->cmsg_type);
	return -1;
      }
      *fd = *((int *) CMSG_DATA(cmsg));
 //printf ("received fd %d\n", *fd);
    } else {
      *fd = -1;
    }
  } else {
    size = read (socket, buf, len);
    *fd = -1;
  }
  return size;
}

MODULE = IO::FDpassData		PACKAGE = IO::FDpassData

PROTOTYPES: ENABLED

int
fd_sendata (socket, b, ...)
	int socket
	SV * b
INIT:
	STRLEN len = 1;
	unsigned char * buf = "\0";	// preset to empty string if message buffer is 'undef'
	int fd;
CODE:
	if (SvOK(b) && SvPOK(b)) {
	  buf = (unsigned char *) SvPV(b,len);
	  if (len == 0)		// zero length string "\0"
	    len = 1;
	}
	if (items == 3 && SvOK(ST(2)) && SvIOK(ST(2))) {
	  fd = (int)SvIV(ST(2));
	} else {
	  fd = -1;
	}
	RETVAL = _fd_sendata(socket, buf, len, fd);
OUTPUT:
	RETVAL


void
fd_recvdata (sock, len)
	int sock
	int len
PREINIT:
	int fd, size;
	unsigned char * buf = malloc(len);
PPCODE:
	size = _fd_recvdata(sock, buf, len, &fd);
	if (size < 0) {			// ERROR
	  free(buf);
	  XSRETURN_EMPTY;
	}
	if (size == 1 && buf[0] == 0x0) {
	  size = 0;
	}
	XPUSHs(sv_2mortal(newSViv(size)));
	if (size == 0) {
	  XPUSHs(sv_2mortal(newSVpvn(buf,0)));
	} else {
	  XPUSHs(sv_2mortal(newSVpvn(buf,size)));
	}
	free(buf);
	if (fd < 0) {
	  XSRETURN(2);
	}
	XPUSHs(sv_2mortal(newSViv(fd)));
	XSRETURN(3);