// This may look like C code, but it is really -*- C++ -*-
// 
// <copyright> 
//  
//  Copyright (c) 1994
//  Institute for Information Processing and Computer Supported New Media (IICM), 
//  Graz University of Technology, Austria. 
//  
// </copyright> 
// 
// 
// <file> 
// 
// Name:        unixsocket.C
// 
// Purpose:     
// 
// Created:     4 May 95   Joerg Faschingbauer
// 
// Modified:    
// 
// Description: 
// 
// 
// </file> 
#include "unixsocket.h"

#include "assert.h"
#include "hgunistd.h"
#include "verbose.h"

#include <string.h>

#include <sys/socket.h>
#include <sys/un.h> /* UNIX domain socket structures */
#include <sys/uio.h> /* iovec */
#include <sys/time.h> /* select() */

#ifndef SOMAXCONN
#  define SOMAXCONN 8
#endif

#if defined(SUN) && HG_OSMaj == 4
#  define HAVE_SENDFD
#endif
#if defined(HPUX) && HG_OSMaj == 9
#  define HAVE_SENDFD
#endif
#if defined(ULTRIX) && HG_OSMaj == 4
#  define HAVE_SENDFD
#endif
#if defined(SUN) && HG_OSMaj == 5
#  define HAVE_SENDFD
#endif
#if defined(IRIX) && HG_OSMaj == 5
#  define HAVE_SENDFD
#endif
#if defined(OSF1) && HG_OSMaj >= 2
#  define HAVE_SENDFD
#endif
#if defined(AIX)
#  define DONT_HAVE_SENDFD
#endif
#if (defined(LINUX) || defined(LINUX_ELF))
#  define DONT_HAVE_SENDFD
#endif
#if defined(BSDI)
#  define DONT_HAVE_SENDFD
#endif

#if ! (defined HAVE_SENDFD || defined DONT_HAVE_SENDFD)
#error SEND_FD
#endif

static const int PATHLEN = sizeof (sockaddr_un) - sizeof (short) - 5 ; // 5?++++
// hgassert (sizeof(sockaddr_un)>=sizeof(sockaddr_in), "Socket::accept(): sockaddr_un too small") ;

// --------------------------------------------------------------------
const char* UNIXSocket :: version1 = "$Id: unixsocket.C,v 1.9 1997/02/21 12:41:25 jfasch Exp $" ;

UNIXSocket :: UNIXSocket()
: Socket() {}

UNIXSocket :: UNIXSocket (int fd) {
   Socket::attach (fd) ;
   // assert blocking vs listening with finer granularity than Socket ++++
}

UNIXSocket :: UNIXSocket (const char* path) {
   connect (path) ;
}

UNIXSocket :: ~UNIXSocket() {
   if (! closed())
      close() ;
}

boolean UNIXSocket :: connect (const char* path) {
   hgassert (fd()<0, "UNIXSocket::connect(): already using a file number") ;
   hgassert (::strlen(path)<PATHLEN, "UNIXSocket::connect(): path too long") ;

   struct sockaddr_un addr ;
   addr.sun_family = AF_UNIX ;
   ::strcpy (addr.sun_path, path) ;
   int addrlen = sizeof (addr) - sizeof (addr.sun_path) + ::strlen (path) ;
   
   int retries = 2 ;
   int sock ;
   do {
      sock = ::socket (AF_UNIX, SOCK_STREAM, 0) ;
      if (sock < 0) {
         DEBUGNL ("UNIXSocket::connect(): could not create socket") ;
         set_errno_(::errno) ;
         perror_("UNIXSocket::connect(): ::socket()") ;
         return false ;
      }
      if (::connect (sock, (struct sockaddr*)&addr, addrlen) < 0) {
         if (::errno == ECONNREFUSED && retries > 0) {
            // try again since peer's backlog may just be full
            ::close (sock) ;
            ::sleep (1) ; // ++++
            continue ;
         } else {
            DEBUGNL ("UNIXSocket::connect(): cannot connect") ;
            set_errno_(::errno) ;
            perror_("UNIXSocket::connect(): ::connect()") ;
            ::close (sock) ;
            return false ;
         }
      }
      break ;
   } while (retries-- > 0) ;
   
   set_fd_(sock) ;
//    set_close_(true) ;
   return true ;
}

boolean UNIXSocket :: listen (const char* path) {
   hgassert (fd()<0, "UNIXSocket::listen(): this already in use") ;
   hgassert (strlen(path)<PATHLEN, "UNIXSocket::listen(): path too long") ;

   int sock = ::socket (AF_UNIX, SOCK_STREAM, 0) ;
   if (sock < 0) {
      DEBUGNL ("UNIXSocket::listen(): could not create socket") ;
      set_errno_(::errno) ;
      perror_("UNIXSocket::listen(): ::socket()") ;
      return false ;
   }

   int pathlen = ::strlen (path) ;
   struct sockaddr_un addr ;
   ::memset (&addr, 0, sizeof (addr)) ; // apparently some systems want this (Solaris?)
   addr.sun_family = AF_UNIX ;
   ::memcpy (addr.sun_path, path, pathlen) ;

   ::unlink (path) ; // steal the service ?  ++++
   
   // have to pass 2 instead of sizeof(...sun_family) because some BSD
   // systems have 2 chars (len and family) instead.
   if (::bind (sock, (struct sockaddr*)&addr, 2 + pathlen) < 0) {
      DEBUGNL ("UNIXSocket::listen(): ::bind() error") ;
      set_errno_(::errno) ;
      perror_("UNIXSocket::listen(): ::bind()") ;
      ::close (sock) ;
      return false ;
   }
   if (::listen (sock, SOMAXCONN) < 0) {
      DEBUGNL ("UNIXSocket::listen(): ::listen() error") ;
      set_errno_(::errno) ;
      perror_("UNIXSocket::listen(): ::listen()") ;
      ::close (sock);
      return false ;
   }

   set_fd_(sock) ;
   set_listening_(true) ;
   return true ;
}

boolean UNIXSocket :: haveSendFd() {
#  ifdef HAVE_SENDFD
   return true ;
#  else
   return false ;
#  endif
}

boolean UNIXSocket :: sendFd (int sfd) {
   hgassert (fd()>=0, "UNIXSocket::sendFd(): not using a file number yet") ;
   hgassert (haveSendFd(), "UNIXSocket::sendFd(): operation not supported") ;

#  ifdef HAVE_SENDFD
   iovec iov ;
   iov.iov_base = nil ;
   iov.iov_len = 0 ;

   msghdr msg ;
   msg.msg_name = nil ;
   msg.msg_namelen = 0 ;
   msg.msg_iov = &iov ;
   msg.msg_iovlen = 1 ;
   msg.msg_accrights = (caddr_t) &sfd ;
   msg.msg_accrightslen = sizeof (sfd) ;

   if (::sendmsg (fd(), &msg, 0) < 0) {
      set_errno_(::errno) ;
      perror_("UNIXSocket::sendFd(): ::sendmsg()") ;
      return false ;
   }
   return true ;
#  else
   return false ; // satisfy compiler
#  endif
}

boolean UNIXSocket :: recvFd (int& rfd) {
   hgassert (fd()>=0, "UNIXSocket::sendFd(): not using a file number yet") ;
   hgassert (haveSendFd(), "UNIXSocket::recvFd(): operation not supported") ;

#  ifdef HAVE_SENDFD
   // recvmsg() seems not to block
   // have to do a blocking select() on my fd.
   fd_set fds ;
   ::memset (&fds, 0, sizeof(fd_set)) ;
   FD_SET (fd(), &fds) ;
#ifdef HPUX
   if (::select (fd()+1, (int*)&fds, nil, nil, nil) < 0)
#else
   if (::select (fd()+1, &fds, nil, nil, nil) < 0)
#endif
      {
         set_errno_(::errno) ;
         perror_("UNIXSocket::recvFd(): ::select()") ;
         return false ;
      }

   // fd seems to be ready now
   iovec iov ;
   iov.iov_base = nil ;
   iov.iov_len = 0 ;

   msghdr msg ;
   msg.msg_name = nil ;
   msg.msg_namelen = 0 ;
   msg.msg_iov = &iov ;
   msg.msg_iovlen = 1 ;
   msg.msg_accrights = (caddr_t) &rfd ;
   msg.msg_accrightslen = sizeof (int) ;

   if (::recvmsg (fd(), &msg, 0) < 0) {
      set_errno_(::errno) ;
      perror_("UNIXSocket::recvFd(): ::recvmsg()") ;
      return false ;
   }
   return true ;
#  else
   return false ; // satisfy compiler
#  endif
}
