/* xiosocketpair.c */
/* Copyright Gerhard Rieger */
/* Published under the GNU General Public License V.2, see file COPYING */

/* this is the source of the internal xiosocketpair function */

#include "xiosysincludes.h"
#include "sycls.h"
#include "compat.h"
#include "error.h"
#include "xio.h"


#if defined(HAVE_DEV_PTMX)
#  define PTMX "/dev/ptmx"	/* Linux */
#elif HAVE_DEV_PTC
#  define PTMX "/dev/ptc"	/* AIX */
#endif

#define MAXPTYNAMELEN 64

int xiopty(int useptmx, int *ttyfdp, int *ptyfdp) {
   int ttyfd, ptyfd = -1;
   char ptyname[MAXPTYNAMELEN];
   struct termios termarg;

   if (useptmx) {
      if ((ptyfd = Open(PTMX, O_RDWR|O_NOCTTY, 0620)) < 0) {
	 Warn1("open(\""PTMX"\", O_RDWR|O_NOCTTY, 0620): %s",
	       strerror(errno));
	 /*!*/
      } else {
	 ;/*0 Info1("open(\""PTMX"\", O_RDWR|O_NOCTTY, 0620) -> %d", ptyfd);*/
      }
      if (ptyfd >= 0) {
	 char *tn = NULL;

	 /* we used PTMX before forking */
	 /*0 extern char *ptsname(int);*/
#if HAVE_GRANTPT	/* AIX, not Linux */
	 if (Grantpt(ptyfd)/*!*/ < 0) {
	    Warn2("grantpt(%d): %s", ptyfd, strerror(errno));
	 }
#endif /* HAVE_GRANTPT */
#if HAVE_UNLOCKPT
	 if (Unlockpt(ptyfd)/*!*/ < 0) {
	    Warn2("unlockpt(%d): %s", ptyfd, strerror(errno));
	 }
#endif /* HAVE_UNLOCKPT */
#if HAVE_PROTOTYPE_LIB_ptsname	/* AIX, not Linux */
	 if ((tn = Ptsname(ptyfd)) == NULL) {
	    Warn2("ptsname(%d): %s", ptyfd, strerror(errno));
	 } else {
	    Notice1("PTY is %s", tn);
	 }
#endif /* HAVE_PROTOTYPE_LIB_ptsname */
#if 0
	 if (tn == NULL) {
	    /*! ttyname_r() */
	    if ((tn = Ttyname(ptyfd)) == NULL) {
	       Warn2("ttyname(%d): %s", ptyfd, strerror(errno));
	    }
	 }
	 ptyname[0] = '\0'; strncat(ptyname, tn, MAXPTYNAMELEN-1);
#endif
	 if ((ttyfd = Open(tn, O_RDWR|O_NOCTTY, 0620)) < 0) {
	    Warn2("open(\"%s\", O_RDWR|O_NOCTTY, 0620): %s", tn, strerror(errno));
	 } else {
	    /*0 Info2("open(\"%s\", O_RDWR|O_NOCTTY, 0620) -> %d", tn, ttyfd);*/
	 }

#ifdef I_PUSH
	 /* Linux: I_PUSH def'd; pty: ioctl(, I_FIND, ...) -> -1 EINVAL */
	 /* AIX:   I_PUSH def'd; pty: ioctl(, I_FIND, ...) -> 1 */
	 /* SunOS: I_PUSH def'd; pty: ioctl(, I_FIND, ...) -> 0 */
	 /* HP-UX: I_PUSH def'd; pty: ioctl(, I_FIND, ...) -> 0 */
	 if (Ioctl(ttyfd, I_FIND, "ldterm") == 0) {
	    Ioctl(ttyfd, I_PUSH, "ptem");		/* 0 */
	    Ioctl(ttyfd, I_PUSH, "ldterm");		/* 0 */
	    Ioctl(ttyfd, I_PUSH, "ttcompat");	/* HP-UX: -1 */
	 }
#endif
      }
   }
#if HAVE_OPENPTY
   if (ptyfd < 0) {
      int result;
      if ((result = Openpty(&ptyfd, &ttyfd, ptyname, NULL, NULL)) < 0) {
	 Error4("openpty(%p, %p, %p, NULL, NULL): %s",
		&ptyfd, &ttyfd, ptyname, strerror(errno));
	 return -1;
      }
      Notice1("PTY is %s", ptyname);
   }
#endif /* HAVE_OPENPTY */

   if (Tcgetattr(ttyfd, &termarg) < 0) {
      Error3("tcgetattr(%d, %p): %s",
	     ttyfd, &termarg, strerror(errno));
   }
#if 0
   cfmakeraw(&termarg);
#else
   /*!!! share code with xioopts.c raw,echo=0 */
   termarg.c_iflag &=
      ~(IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK|ISTRIP|INLCR|IGNCR|ICRNL|IXON|IXOFF
#ifdef IUCLC
	|IUCLC
#endif
	|IXANY|IMAXBEL);
   termarg.c_iflag |= (0);
   termarg.c_oflag &= ~(OPOST);
   termarg.c_oflag |= (0);
   termarg.c_cflag &= ~(0);
   termarg.c_cflag |= (0);
   termarg.c_lflag &= ~(ECHO|ECHONL|ISIG|ICANON
#ifdef XCASE
			|XCASE
#endif
			);
   termarg.c_lflag |= (0);
   termarg.c_cc[VMIN] = 1;
   termarg.c_cc[VTIME] = 0;
#endif
   if (Tcsetattr(ttyfd, TCSADRAIN, &termarg) < 0) {
      Error3("tcsetattr(%d, TCSADRAIN, %p): %s",
	     ttyfd, &termarg, strerror(errno));
   }

   *ttyfdp = ttyfd;
   *ptyfdp = ptyfd;
   return 0;
}

/* generates a socket pair; supports not only PF_UNIX but also PF_INET */
int xiosocketpair2(int pf, int socktype, int protocol, int sv[2]) {
   int result;

   switch (pf) {
      struct sockaddr_in ssin, csin, xsin;	/* server, client, compare */
      socklen_t cslen, xslen;
      int sconn, slist, sserv;	/* socket FDs */

   case PF_UNIX:
      result = Socketpair(pf, socktype, protocol, sv);
      if (result < 0) {
	 Error5("socketpair(%d, %d, %d, %p): %s",
		pf, socktype, protocol, sv, strerror(errno));
	 return -1;
      }
      break;
#if LATER
   case PF_INET:
#if 1 /*!!! Linux */
      ssin.sin_family = pf;
      ssin.sin_port = htons(1024+random()%(65536-1024));
      ssin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
#endif /*  */
      if ((s = Socket(pf, socktype, protocol)) < 0) {
	 Error4("socket(%d, %d, %d): %s",
		pf, socktype, protocol, strerror(errno));
      }
      if (Bind(s, (struct sockaddr *)&ssin, sizeof(ssin)) < 0) {
	 Error6("bind(%d, {%u, 0x%x:%u}, "F_Zu"): %s",
		s, ssin.sin_family, ssin.sin_addr.s_addr, ssin.sin_port,
		sizeof(ssin), strerror(errno));
      }
      if (Connect(s, (struct sockaddr *)&ssin, sizeof(ssin)) < 0) {
	 Error6("connect(%d, {%u, 0x%x:%u}, "F_Zu"): %s",
		s, ssin.sin_family, ssin.sin_addr.s_addr, ssin.sin_port,
		sizeof(ssin), strerror(errno));
	 return -1;
      }
      break;
#endif /* LATER */
   case PF_INET:
      ssin.sin_family = pf;
      ssin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
      if ((slist = Socket(pf, socktype, protocol)) < 0) {
	 Error4("socket(%d, %d, %d): %s",
		pf, socktype, protocol, strerror(errno));
      }
      while (true) {	/* find a port we can bind to */
	 ssin.sin_port = htons(1024+random()%(65536-1024));
	 if (Bind(slist, (struct sockaddr *)&ssin, sizeof(ssin)) == 0)  break;
	 if (errno == EADDRINUSE) {
	    Info6("bind(%d, {%u, 0x%x:%u}, "F_Zu"): %s",
		  slist, ssin.sin_family, ssin.sin_addr.s_addr,
		  ntohs(ssin.sin_port), sizeof(ssin), strerror(errno));
	    continue;
	 }
	 Error6("bind(%d, {%u, 0x%x:%u}, "F_Zu"): %s",
		slist, ssin.sin_family, ssin.sin_addr.s_addr, ssin.sin_port,
		sizeof(ssin), strerror(errno));
	 Close(slist);
	 return -1;
      }
      Listen(slist, 0);
      if ((sconn = Socket(pf, socktype, protocol)) < 0) {
	 Error4("socket(%d, %d, %d): %s",
		pf, socktype, protocol, strerror(errno));
	 Close(slist); return -1;
      }
      /* for testing race condition: Sleep(30); */
      if (Connect(sconn, (struct sockaddr *)&ssin, sizeof(ssin)) < 0) {
	 Error6("connect(%d, {%u, 0x%x:%u}, "F_Zu"): %s",
		sconn, ssin.sin_family, ssin.sin_addr.s_addr,
		ntohs(ssin.sin_port),
		sizeof(ssin), strerror(errno));
	 Close(slist); Close(sconn); return -1;
      }
      cslen = sizeof(csin);
      if (Getsockname(sconn, (struct sockaddr *)&csin, &cslen) < 0) {
	 Error4("getsockname(%d, %p, %p): %s",
		sconn, &csin, &cslen, strerror(errno));
	 Close(slist); Close(sconn); return -1;
      }
      do {
	 xslen = sizeof(xsin);
	 if ((sserv = Accept(slist, (struct sockaddr *)&xsin, &xslen)) < 0) {
	    Error4("accept(%d, %p, {"F_Zu"}): %s",
		   slist, &csin, sizeof(xslen), strerror(errno));
	    Close(slist); Close(sconn); return -1;
	 }
	 if (!memcmp(&csin, &xsin, cslen)) {
	    break;	/* expected connection */
	 }
	 Warn4("unexpected connection to 0x%lx:%hu from 0x%lx:%hu",
	       ntohl(ssin.sin_addr.s_addr), ntohs(ssin.sin_port),
	       ntohl(xsin.sin_addr.s_addr), ntohs(xsin.sin_port));
      } while (true);
      Close(slist);
      sv[0] = sconn;
      sv[1] = sserv;
      break;
   default:
      Error1("xiosocketpair2(): pf=%u not implemented", pf);
      return -1;
   }
   return 0;
}

/*
   dual should only be != 0 when both directions are used
   returns 0 on success
 */
int xiocommpair(int commtype, bool lefttoright, bool righttoleft,
		int dual, xiofd_t *left, xiofd_t *right, ...) {
   va_list ap;
   int domain = -1, socktype = -1, protocol = -1;
   int useptmx = 0;
   /* arrays can be used with pipe(2) and socketpair(2): */
   int svlr[2] = {-1, -1};	/* left to right: rfd, wfd */
   int svrl[2] = {-1, -1};	/* right to left: rfd, wfd */

   /* get related parameters from parameter list */
   switch (commtype) {
   case XIOCOMM_SOCKETPAIR:
   case XIOCOMM_SOCKETPAIRS:
      va_start(ap, right);
      domain   = va_arg(ap, int);
      socktype = va_arg(ap, int);
      protocol = va_arg(ap, int);
      va_end(ap);
      break;
   case XIOCOMM_PTY:
   case XIOCOMM_PTYS:
      va_start(ap, right);
      useptmx = va_arg(ap, int);
      va_end(ap);
      break;
   default:
      break;
   }

   switch (commtype) {
   default: /* unspec */
      Warn1("internal: undefined communication type %d, defaulting to 0",
	    commtype);
      commtype = 0;
      /*PASSTHROUGH*/
   case XIOCOMM_SOCKETPAIRS: /* two socketpairs - the default */
      if (lefttoright) {
	 if (Socketpair(domain, socktype, protocol, svlr) < 0) {
	    Error5("socketpair(%d, %d, %d, %p): %s",
		   domain, socktype, protocol, svlr, strerror(errno));
	 }
	 Shutdown(svlr[0], SHUT_WR);
      }	 
      if (righttoleft) {
	 if (Socketpair(domain, socktype, protocol, svrl) < 0) {
	    Error5("socketpair(%d, %d, %d, %p): %s",
		   domain, socktype, protocol, svrl, strerror(errno));
	 }
	 Shutdown(svrl[0], SHUT_WR);
      }
      left->single     = right->single     = false;
      left->dtype      = right->dtype      = XIODATA_STREAM;
      left->howtoshut  = right->howtoshut  = XIOSHUT_DOWN;
      left->howtoclose = right->howtoclose = XIOCLOSE_CLOSE;
      break;

   case XIOCOMM_PTYS: /* two ptys in raw mode, EOF in canonical mode */
      if (lefttoright) {
	 if (xiopty(useptmx, &svlr[0], &svlr[1]) < 0)  return -1;
	 /* pty is write side, interpretes ^D in canonical mode */
      }
      if (righttoleft) {
	 if (xiopty(useptmx, &svrl[0], &svrl[1]) < 0)  return -1;
      }
      left->single     = right->single     = false;
      left->dtype      = right->dtype      = XIODATA_PTY;
      left->howtoshut  = right->howtoshut  = XIOSHUT_PTYEOF;
      left->howtoclose = right->howtoclose = XIOCLOSE_CLOSE;
      break;

   case XIOCOMM_SOCKETPAIR: /* one socketpair */
      if (Socketpair(domain, socktype, protocol, svlr) < 0) {
	 Error5("socketpair(%d %d %d, %p): %s",
		domain, socktype, protocol, svlr, strerror(errno));
	 return -1;
      }
      left->single     = right->single     = true;
      left->dtype      = right->dtype      = XIODATA_STREAM;
      left->howtoshut  = right->howtoshut  = XIOSHUT_DOWN;
      left->howtoclose = right->howtoclose = XIOCLOSE_CLOSE;
      break;

   case XIOCOMM_PTY: /* one pty in raw mode, EOF in canonical mode */
      if (xiopty(useptmx, &svlr[0], &svlr[1]) < 0)  return -1;
      left->single     = right->single     = true;
      left->dtype      = right->dtype      = XIODATA_PTY;
      left->howtoshut  = XIOSHUT_PTYEOF;
      right->howtoshut = XIOSHUT_CLOSE;
      left->howtoclose = right->howtoclose = XIOCLOSE_CLOSE;
      break;

   case XIOCOMM_PIPES: /* two pipes */
      if (lefttoright) {
	 if (Pipe(svlr) < 0) {
	    Error2("pipe(%p): %s", svlr, strerror(errno));
	    return -1;
	 }
      }
      if (righttoleft) {
	 if (Pipe(svrl) < 0) {
	    Error2("pipe(%p): %s", svrl, strerror(errno));
	    return -1;
	 }
      }
      left->single     = right->single     = false;
      left->dtype      = right->dtype      = XIODATA_STREAM;
      left->howtoshut  = right->howtoshut  = XIOSHUT_CLOSE;
      left->howtoclose = right->howtoclose = XIOCLOSE_CLOSE;
      break;

   case XIOCOMM_TCP:
   case XIOCOMM_TCP4: /* one TCP/IPv4 socket pair */
      if (xiosocketpair2(PF_INET, SOCK_STREAM, 0, svlr) < 0) {
	 Error2("socketpair(PF_UNIX, PF_STREAM, 0, %p): %s",
		svlr, strerror(errno));
	 return -1;
      }
      left->single     = right->single     = true;
      left->dtype      = right->dtype      = XIODATA_STREAM;
      left->howtoshut  = right->howtoshut  = XIOSHUT_DOWN;
      left->howtoclose = right->howtoclose = XIOCLOSE_CLOSE;
      break;
   }

   if (dual && left->single) {
      /* one pair */
      /* dual; we use different FDs for the channels to avoid conflicts
	 (happened in dual exec) */
      if ((svrl[1] = Dup(svlr[0])) < 0) {
	 Error2("dup(%d): %s", svrl[0], strerror(errno));
	 return -1;
      }
      if ((svrl[0] = Dup(svlr[1])) < 0) {
	 Error2("dup(%d): %s", svlr[1], strerror(errno));
	 return -1;
      }
   } else if (left->single) {
      svrl[1] = svlr[0];
      svrl[0] = svlr[1];
   }

   /* usually they are not to be passed to exec'd child processes */
   if (lefttoright) {
      Fcntl_l(svlr[0], F_SETFD, 1);
      Fcntl_l(svlr[1], F_SETFD, 1);
   }
   if (righttoleft && (!left->single || dual)) {
      Fcntl_l(svrl[0], F_SETFD, 1);
      Fcntl_l(svrl[1], F_SETFD, 1);
   }

   left->rfd = svrl[0];
   left->wfd = svlr[1];
   right->rfd = svlr[0];
   right->wfd = svrl[1];
   Notice4("xiocommpair() -> [%d:%d], [%d:%d]",
	   left->rfd, left->wfd, right->rfd, right->wfd);
   return 0;
}