/*
 *  Copyright(c), 2002 The GHC Team.
 */

#ifdef aix_HOST_OS
#define _LINUX_SOURCE_COMPAT 
// Required to get CMSG_SPACE/CMSG_LEN macros.  See #265.
// Alternative is to #define COMPAT_43 and use the 
// HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS code instead, but that means
// fiddling with the configure script too.
#endif

#include "HsNet.h"
#include <string.h>

#if HAVE_STRUCT_MSGHDR_MSG_CONTROL || HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS /* until end */

/* 
 *  Support for transmitting file descriptors.
 *
 *  
 */
 

/*
 * sendmsg() and recvmsg() wrappers for transmitting
 * ancillary socket data.
 *
 * Doesn't provide the full generality of either, specifically:
 *
 *  - no support for scattered read/writes.
 *  - only possible to send one ancillary chunk of data at a time.
 *
 *
 * NOTE: recv/sendAncillary() is being phased out in preference
 *       of the more specific send/recvFd(), as the latter is
 *       really the only application of recv/sendAncillary() and
 *       stand the chance of being supported on platforms that
 *       don't have send/recvmsg() (but do have ioctl() support
 *       for this kind of thing, for instance.)
 *
 */

int
sendFd(int sock,
       int outfd)
{
  struct msghdr msg = {0};
  struct iovec iov[1];
  char  buf[2];
#if HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS
  msg.msg_accrights = (void*)&outfd;
  msg.msg_accrightslen = sizeof(int);
#else
  struct cmsghdr *cmsg;
  char ancBuffer[CMSG_SPACE(sizeof(int))];
  char* dPtr;
  
  msg.msg_control = ancBuffer;
  msg.msg_controllen = sizeof(ancBuffer);

  cmsg = CMSG_FIRSTHDR(&msg);
  cmsg->cmsg_level = SOL_SOCKET;
  cmsg->cmsg_type = SCM_RIGHTS;
  cmsg->cmsg_len = CMSG_LEN(sizeof(int));
  dPtr = (char*)CMSG_DATA(cmsg);
  
  *(int*)dPtr = outfd;
  msg.msg_controllen = cmsg->cmsg_len;
#endif

  buf[0] = 0; buf[1] = '\0';
  iov[0].iov_base = buf;
  iov[0].iov_len  = 2;

  msg.msg_iov = iov;
  msg.msg_iovlen = 1;
  
  return sendmsg(sock,&msg,0);
}

int
sendAncillary(int sock,
	      int level,
	      int type,
	      int flags,
	      void* data,
	      int len)
{
  struct msghdr msg = {0};
  struct iovec iov[1];
  char  buf[2];
#if HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS
  /* Contains the older BSD msghdr fields only, so no room
     for 'type' or 'level' data.
  */
  msg.msg_accrights = data;
  msg.msg_accrightslen=len;
#else
  struct cmsghdr *cmsg;
  char ancBuffer[CMSG_SPACE(len)];
  char* dPtr;
  
  msg.msg_control = ancBuffer;
  msg.msg_controllen = sizeof(ancBuffer);

  cmsg = CMSG_FIRSTHDR(&msg);
  cmsg->cmsg_level = level;
  cmsg->cmsg_type = type;
  cmsg->cmsg_len = CMSG_LEN(len);
  dPtr = (char*)CMSG_DATA(cmsg);
  
  memcpy(dPtr, data, len);
  msg.msg_controllen = cmsg->cmsg_len;
#endif
  buf[0] = 0; buf[1] = '\0';
  iov[0].iov_base = buf;
  iov[0].iov_len  = 2;

  msg.msg_iov = iov;
  msg.msg_iovlen = 1;
  
  return sendmsg(sock,&msg,flags);
}

int
recvFd(int sock)
{
  struct msghdr msg = {0};
  char  duffBuf[10];
  int rc;
  int len = sizeof(int);
  struct iovec iov[1];
#if HAVE_STRUCT_MSGHDR_MSG_CONTROL
  struct cmsghdr *cmsg = NULL;
  struct cmsghdr *cptr;
#else
  int* fdBuffer;
#endif

  iov[0].iov_base = duffBuf;
  iov[0].iov_len  = sizeof(duffBuf);
  msg.msg_iov = iov;
  msg.msg_iovlen = 1;

#if HAVE_STRUCT_MSGHDR_MSG_CONTROL
  cmsg = (struct cmsghdr*)malloc(CMSG_SPACE(len));
  if (cmsg==NULL) {
    return -1;
  }
  
  msg.msg_control = (void *)cmsg;
  msg.msg_controllen = CMSG_LEN(len);
#else
  fdBuffer = (int*)malloc(len);
  if (fdBuffer) {
    msg.msg_accrights    = (void *)fdBuffer;
  } else {
    return -1;
  }
  msg.msg_accrightslen = len;
#endif

  if ((rc = recvmsg(sock,&msg,0)) < 0) {
    return rc;
  }
  
#if HAVE_STRUCT_MSGHDR_MSG_CONTROL
  cptr = (struct cmsghdr*)CMSG_FIRSTHDR(&msg);
  return *(int*)CMSG_DATA(cptr);
#else
  return *(int*)fdBuffer;
#endif
}


int
recvAncillary(int  sock,
	      int* pLevel,
	      int* pType,
	      int  flags,
	      void** pData,
	      int* pLen)
{
  struct msghdr msg = {0};
  char  duffBuf[10];
  int rc;
  struct iovec iov[1];
#if HAVE_STRUCT_MSGHDR_MSG_CONTROL
  struct cmsghdr *cmsg = NULL;
  struct cmsghdr *cptr;
#endif
  
  iov[0].iov_base = duffBuf;
  iov[0].iov_len  = sizeof(duffBuf);
  msg.msg_iov = iov;
  msg.msg_iovlen = 1;

#if HAVE_STRUCT_MSGHDR_MSG_CONTROL
  cmsg = (struct cmsghdr*)malloc(CMSG_SPACE(*pLen));
  if (cmsg==NULL) {
    return -1;
  }
  
  msg.msg_control = (void *)cmsg;
  msg.msg_controllen = CMSG_LEN(*pLen);
#else
  *pData = (void*)malloc(*pLen);
  if (*pData) {
      msg.msg_accrights    = *pData;
  } else {
      return -1;
  }
  msg.msg_accrightslen = *pLen;
#endif

  if ((rc = recvmsg(sock,&msg,flags)) < 0) {
    return rc;
  }
  
#if HAVE_STRUCT_MSGHDR_MSG_CONTROL
  cptr = (struct cmsghdr*)CMSG_FIRSTHDR(&msg);

  *pLevel = cptr->cmsg_level;
  *pType  = cptr->cmsg_type;
  /* The length of the data portion only */
  *pLen   = cptr->cmsg_len - sizeof(struct cmsghdr);
  *pData  = CMSG_DATA(cptr);
#else
  /* Sensible defaults, I hope.. */
  *pLevel = 0;
  *pType  = 0;
#endif

  return rc;
}
#endif
