/* source: xioopen.c */
/* Copyright Gerhard Rieger and contributors (see file CHANGES) */
/* Published under the GNU General Public License V.2, see file COPYING */

/* this is the source file of the extended open function */

#include "xiosysincludes.h"

#include "xioopen.h"
#include "xiomodes.h"
#include "nestlex.h"

#include "xiosigchld.h"

void *xioopenleftthenengine(void *thread_void);
static xiosingle_t *xioparse_single(const char **addr);

static int 
   xioopen_inter_single(xiofile_t *xfd, int xioflags);
static int 
   xioopen_endpoint_single(xiofile_t *xfd, int xioflags);
static int
   xioopen_unoverload(xiosingle_t *sfd,
		      int mayleft,	/* what may be on left side: or'd
					   XIOBIT_RDWR, XIOBIT_RDONLY,
					   XIOBIT_WRONLY */
		      int *isleft,	/* what the selected address desc
					   provides on left side: XIO_RDWR,
					   XIO_RDONLY, or XIO_WRONLY */
		      int mayright,	/* what may be on right side: or'd
					   XIOBIT_RDWR, XIOBIT_RDONLY,
					   XIOBIT_WRONLY */
		      int *isright);	/* what the selected address desc
					   provides on right side: XIO_RDWR,
					   XIO_RDONLY, or XIO_WRONLY */


const struct xioaddrname address_names[] = {
#if 1
#if WITH_STDIO
   { "-",		xioaddrs_stdio },
#endif
#if defined(WITH_UNIX) && defined(WITH_ABSTRACT_UNIXSOCKET)
   { "abstract",		xioaddrs_abstract_client },
   { "abstract-client",		xioaddrs_abstract_client },
   { "abstract-connect",	xioaddrs_abstract_connect },
#if WITH_LISTEN
   { "abstract-listen",		xioaddrs_abstract_listen },
#endif
   { "abstract-recv",		xioaddrs_abstract_recv },
   { "abstract-recvfrom",	xioaddrs_abstract_recvfrom },
   { "abstract-sendto",		xioaddrs_abstract_sendto },
#endif /* defined(WITH_UNIX) && defined(WITH_ABSTRACT_UNIXSOCKET) */
#if WITH_CREAT
   { "creat",		xioaddrs_creat },
   { "create",		xioaddrs_creat },
#endif
#if WITH_GENERICSOCKET
   { "datagram",		xioaddrs_socket_datagram },
   { "dgram",			xioaddrs_socket_datagram },
#endif
#if WITH_PIPE
   { "echo",		xioaddrs_pipe },
#endif
#if WITH_EXEC
   { "exec",		xioaddrs_exec },
   { "exec1",		xioaddrs_exec1 },
   { "exec2",		xioaddrs_exec },
#endif
#if WITH_FDNUM
   { "fd",		xioaddrs_fdnum },
#endif
#if WITH_PIPE
   { "fifo",		xioaddrs_pipe },
#endif
#if WITH_FILE
   { "file",		xioaddrs_open },
#endif
#if WITH_GOPEN
   { "gopen",		xioaddrs_gopen },
#endif
#if WITH_INTERFACE
   { "if",		xioaddrs_interface },
#endif
#if (WITH_IP4 || WITH_IP6) && WITH_TCP
   { "inet",		xioaddrs_tcp_connect },
#endif
#if (WITH_IP4 || WITH_IP6) && WITH_TCP && WITH_LISTEN
   { "inet-l",		xioaddrs_tcp_listen },
   { "inet-listen",	xioaddrs_tcp_listen },
#endif
#if WITH_IP4 && WITH_TCP
   { "inet4",		xioaddrs_tcp4_connect },
#endif
#if WITH_IP4 && WITH_TCP && WITH_LISTEN
   { "inet4-l",		xioaddrs_tcp4_listen },
   { "inet4-listen",	xioaddrs_tcp4_listen },
#endif
#if WITH_IP6 && WITH_TCP
   { "inet6",		xioaddrs_tcp6_connect },
#endif
#if WITH_IP6 && WITH_TCP && WITH_LISTEN
   { "inet6-l",		xioaddrs_tcp6_listen },
   { "inet6-listen",	xioaddrs_tcp6_listen },
#endif
#if WITH_INTERFACE
   { "interface",	xioaddrs_interface },
#endif
#if WITH_RAWIP
#if (WITH_IP4 || WITH_IP6)
   { "ip",		xioaddrs_rawip_sendto },
   { "ip-datagram",	xioaddrs_rawip_datagram },
   { "ip-dgram",	xioaddrs_rawip_datagram },
   { "ip-recv",		xioaddrs_rawip_recv },
   { "ip-recvfrom",	xioaddrs_rawip_recvfrom },
   { "ip-send",		xioaddrs_rawip_sendto },
   { "ip-sendto",	xioaddrs_rawip_sendto },
#endif
#if WITH_IP4
   { "ip4",		xioaddrs_rawip4_sendto },
   { "ip4-datagram",	xioaddrs_rawip4_datagram },
   { "ip4-dgram",	xioaddrs_rawip4_datagram },
   { "ip4-recv",	xioaddrs_rawip4_recv },
   { "ip4-recvfrom",	xioaddrs_rawip4_recvfrom },
   { "ip4-send",	xioaddrs_rawip4_sendto },
   { "ip4-sendto",	xioaddrs_rawip4_sendto },
#endif
#if WITH_IP6
   { "ip6",		xioaddrs_rawip6_sendto },
   { "ip6-datagram",	xioaddrs_rawip6_datagram },
   { "ip6-dgram",	xioaddrs_rawip6_datagram },
   { "ip6-recv",	xioaddrs_rawip6_recv },
   { "ip6-recvfrom",	xioaddrs_rawip6_recvfrom },
   { "ip6-send",	xioaddrs_rawip6_sendto },
   { "ip6-sendto",	xioaddrs_rawip6_sendto },
#endif
#endif /* WITH_RAWIP */
#if WITH_UNIX
   { "local",		xioaddrs_unix_connect },
#endif
#if WITH_NOP
   { "nop",		xioaddrs_nop },
#endif
#if WITH_FILE
   { "open",		xioaddrs_open },
#endif
#if WITH_OPENSSL
   { "openssl",		xioaddrs_openssl_connect },
   { "openssl-client",	xioaddrs_openssl_connect },
   { "openssl-connect",	xioaddrs_openssl_connect },
#if WITH_LISTEN
   { "openssl-listen",	xioaddrs_openssl_listen },
   { "openssl-server",	xioaddrs_openssl_listen },
#endif
#endif
#if WITH_PIPE
   { "pipe",		xioaddrs_pipe },
#endif
#if WITH_PROXY
   { "proxy",		xioaddrs_proxy_connect },
   { "proxy-connect",	xioaddrs_proxy_connect },
#endif
#if WITH_PTY
   { "pty",		xioaddrs_pty },
#endif
#if WITH_READLINE
   { "readline",	xioaddrs_readline },
#endif
#if (WITH_IP4 || WITH_IP6) && WITH_SCTP
   { "sctp",		xioaddrs_sctp_connect },
   { "sctp-connect",	xioaddrs_sctp_connect },
#if WITH_LISTEN
   { "sctp-l",		xioaddrs_sctp_listen },
   { "sctp-listen",	xioaddrs_sctp_listen },
#endif
#if WITH_IP4
   { "sctp4",		xioaddrs_sctp4_connect },
   { "sctp4-connect",	xioaddrs_sctp4_connect },
#if WITH_LISTEN
   { "sctp4-l",		xioaddrs_sctp4_listen },
   { "sctp4-listen",	xioaddrs_sctp4_listen },
#endif
#endif /* WITH_IP4 */
#if WITH_IP6
   { "sctp6",		xioaddrs_sctp6_connect },
   { "sctp6-connect",	xioaddrs_sctp6_connect },
#if WITH_LISTEN
   { "sctp6-l",		xioaddrs_sctp6_listen },
   { "sctp6-listen",	xioaddrs_sctp6_listen },
#endif
#endif /* WITH_IP6 */
#endif /* (WITH_IP4 || WITH_IP6) && WITH_SCTP */
#if WITH_GENERICSOCKET
   { "sendto",			xioaddrs_socket_sendto },
#endif
#if WITH_GENERICSOCKET
   { "socket-connect",		xioaddrs_socket_connect },
   { "socket-datagram",		xioaddrs_socket_datagram },
#if WITH_LISTEN
   { "socket-listen",		xioaddrs_socket_listen },
#endif /* WITH_LISTEN */
   { "socket-recv",		xioaddrs_socket_recv },
   { "socket-recvfrom",		xioaddrs_socket_recvfrom },
   { "socket-sendto",		xioaddrs_socket_sendto },
#endif
#if WITH_SOCKS4
   { "socks",		xioaddrs_socks4_connect },
   { "socks-client",	xioaddrs_socks4_connect },
   { "socks4",		xioaddrs_socks4_connect },
   { "socks4-client",	xioaddrs_socks4_connect },
#endif
#if WITH_SOCKS4A
   { "socks4a",		xioaddrs_socks4a_connect },
   { "socks4a-client",	xioaddrs_socks4a_connect },
#endif
#if WITH_SOCKS5
   { "socks5",		xioaddrs_socks5_client },
   { "socks5-client",	xioaddrs_socks5_client },
#endif
#if WITH_OPENSSL
   { "ssl",		xioaddrs_openssl_connect },
#if WITH_LISTEN
   { "ssl-l",		xioaddrs_openssl_listen },
   { "ssl-s",		xioaddrs_openssl_listen },
#endif
#endif
#if WITH_STDIO
   { "stderr",		xioaddrs_stderr },
   { "stdin",		xioaddrs_stdin },
   { "stdio",		xioaddrs_stdio },
   { "stdout",		xioaddrs_stdout },
#endif
#if WITH_SYSTEM
   { "system",		xioaddrs_system },
   { "system1",		xioaddrs_system1 },
   { "system2",		xioaddrs_system },
#endif
#if (WITH_IP4 || WITH_IP6) && WITH_TCP
   { "tcp",		xioaddrs_tcp_connect },
   { "tcp-connect",	xioaddrs_tcp_connect },
#endif
#if (WITH_IP4 || WITH_IP6) && WITH_TCP && WITH_LISTEN
   { "tcp-l",		xioaddrs_tcp_listen },
   { "tcp-listen",	xioaddrs_tcp_listen },
#endif
#if WITH_IP4 && WITH_TCP
   { "tcp4",		xioaddrs_tcp4_connect },
   { "tcp4-connect",	xioaddrs_tcp4_connect },
#endif
#if WITH_IP4 && WITH_TCP && WITH_LISTEN
   { "tcp4-l",		xioaddrs_tcp4_listen },
   { "tcp4-listen",	xioaddrs_tcp4_listen },
#endif
#if WITH_IP6 && WITH_TCP
   { "tcp6",		xioaddrs_tcp6_connect },
   { "tcp6-connect",	xioaddrs_tcp6_connect },
#endif
#if WITH_IP6 && WITH_TCP && WITH_LISTEN
   { "tcp6-l",		xioaddrs_tcp6_listen },
   { "tcp6-listen",	xioaddrs_tcp6_listen },
#endif
#if WITH_TEST
   { "test",		xioaddrs_test },
   { "testrev",		xioaddrs_testrev },
   { "testuni",		xioaddrs_testuni },
#endif
#if WITH_TUN
   { "tun",		xioaddrs_tun },
#endif
#if (WITH_IP4 || WITH_IP6) && WITH_UDP
   { "udp",		xioaddrs_udp_connect },
   { "udp-connect",	xioaddrs_udp_connect },
   { "udp-datagram",	xioaddrs_udp_datagram },
   { "udp-dgram",	xioaddrs_udp_datagram },
#endif
#if (WITH_IP4 || WITH_IP6) && WITH_UDP && WITH_LISTEN
   { "udp-l",	xioaddrs_udp_listen },
   { "udp-listen",	xioaddrs_udp_listen },
#endif
#if (WITH_IP4 || WITH_IP6) && WITH_UDP
   { "udp-recv",	xioaddrs_udp_recv },
   { "udp-recvfrom",	xioaddrs_udp_recvfrom },
   { "udp-send",	xioaddrs_udp_sendto },
   { "udp-sendto",	xioaddrs_udp_sendto },
#endif
#if WITH_IP4 && WITH_UDP
   { "udp4",		xioaddrs_udp4_connect },
   { "udp4-connect",	xioaddrs_udp4_connect },
   { "udp4-datagram",	xioaddrs_udp4_datagram },
   { "udp4-dgram",	xioaddrs_udp4_datagram },
#endif
#if WITH_IP4 && WITH_UDP && WITH_LISTEN
   { "udp4-l",		xioaddrs_udp4_listen },
   { "udp4-listen",	xioaddrs_udp4_listen },
#endif
#if WITH_IP4 && WITH_UDP
   { "udp4-recv",	xioaddrs_udp4_recv },
   { "udp4-recvfrom",	xioaddrs_udp4_recvfrom },
   { "udp4-send",	xioaddrs_udp4_sendto },
   { "udp4-sendto",	xioaddrs_udp4_sendto },
#endif
#if WITH_IP6 && WITH_UDP
   { "udp6",		xioaddrs_udp6_connect },
   { "udp6-connect",	xioaddrs_udp6_connect },
   { "udp6-datagram",	xioaddrs_udp6_datagram },
   { "udp6-dgram",	xioaddrs_udp6_datagram },
#endif
#if WITH_IP6 && WITH_UDP && WITH_LISTEN
   { "udp6-l",		xioaddrs_udp6_listen },
   { "udp6-listen",	xioaddrs_udp6_listen },
#endif
#if WITH_IP6 && WITH_UDP
   { "udp6-recv",	xioaddrs_udp6_recv },
   { "udp6-recvfrom",	xioaddrs_udp6_recvfrom },
   { "udp6-send",	xioaddrs_udp6_sendto },
   { "udp6-sendto",	xioaddrs_udp6_sendto },
#endif
#if WITH_UNIX
   { "unix",		xioaddrs_unix_client },
   { "unix-client",	xioaddrs_unix_client },
   { "unix-connect",	xioaddrs_unix_connect },
#endif
#if WITH_UNIX && WITH_LISTEN
   { "unix-l",		xioaddrs_unix_listen },
   { "unix-listen",	xioaddrs_unix_listen },
#endif
#if WITH_UNIX
   { "unix-recv",	xioaddrs_unix_recv },
   { "unix-recvfrom",	xioaddrs_unix_recvfrom },
   { "unix-send",	xioaddrs_unix_sendto },
   { "unix-sendto",	xioaddrs_unix_sendto },
#endif
#else /* !0 */
#  if WITH_INTEGRATE
#    include "xiointegrate.c"
#  else
#    include "xioaddrtab.c"
#  endif
#endif /* !0 */
   { NULL }	/* end marker */
} ;


/* prepares a xiofile_t record for dual address type:
   sets the tag, creates the references, sets tags and flags
*/
int xioopen_makedual(xiofile_t *file, xiosingle_t *left, xiosingle_t *right) {
   file->tag = XIO_TAG_DUAL;
   file->common.flags = XIO_RDWR;
   file->dual.stream[0] = left;
   left->flags = XIO_RDONLY;
   file->dual.stream[1] = right;
   right->flags = XIO_WRONLY;
   Debug2("dual xfd %p gets left/read xfd %p",   file, left);
   Debug2("dual xfd %p gets right/write xfd %p", file, right);
   return 0;
}

/* returns NULL if an error occurred */
xiofile_t *xioallocfd(void) {
   xiofile_t *fd;

   if ((fd = Calloc(1, sizeof(xiofile_t))) == NULL) {
      return NULL;
   }
   /* some default values; 0's and NULL's need not be applied (calloc'ed) */
   fd->common.tag       = XIO_TAG_INVALID;
/* fd->common.addr      = NULL; */
   fd->common.flags     = XIO_RDWR;

#if WITH_RETRY
/* fd->stream.retry	= 0; */
/* fd->stream.forever	= false; */
   fd->stream.intervall.tv_sec  = 1;
/* fd->stream.intervall.tv_nsec = 0; */
#endif /* WITH_RETRY */
/* fd->common.ignoreeof = false; */
/* fd->common.eof       = 0; */

   fd->stream.rfd       = -1;
   fd->stream.wfd       = -1;
   fd->stream.dtype     = XIODATA_STREAM;
#if _WITH_SOCKET
/* fd->stream.salen     = 0; */
#endif /* _WITH_SOCKET */
/* fd->stream.howtoshut = XIOSHUT_UNSPEC;*/
/* fd->stream.howtoclose  = XIOCLOSE_UNSPEC;*/
/* fd->stream.name      = NULL; */
   fd->stream.escape	= -1;
/* fd->stream.para.exec.pid = 0; */
   fd->stream.lineterm  = LINETERM_RAW;

   /*!! support n socks */
   if (!sock[0]) {
      sock[0] = fd;
   } else {
      sock[1] = fd;
   }
   return fd;
}

void xiofreefd(xiofile_t *xfd) {
   if (xfd->stream.opts != NULL) {
      dropopts(xfd->stream.opts, PH_ALL);
   }
   free(xfd);
}


/* handle one chain of addresses
   rw is one of XIO_RDWR, XIO_RDONLY, or XIO_WRONLY
   when finished with this and the following sub addresses we return an xfd
   that can be used by the _socat() loop
*/
xiofile_t *socat_open(const char *addrs0, int rw, int flags) {
   const char *addrs;
   xiosingle_t *sfdA;	/* what we just parse(d) */
   xiosingle_t *sfdB;	/* what we just parse(d) - second part of dual */
   int newpipesep = 1;	/* dual address: 1%0 instead of 0!!1 */
   int reverseA, reverseB=0;
   bool currentisendpoint = false;
   int mayleftA, mayrightA,  mayleftB, mayrightB;
   int isleftA,  isrightA,   isleftB,  isrightB;
   int srchleftA, srchrightA, srchleftB=0, srchrightB=0;
   xiofile_t *xfd0;	/* what we return */
   xiofile_t *xfd1;	/* left hand of engine */
   xiofile_t *xfd2;	/* returned by sub address */
   int rw0, rw1, rw2;	/* the data directions for respective xfd
				 directions are sepcified as seen by transfer
				 engine */
   xiofd_t left, right;
   struct threadarg_struct *thread_arg;
   /*0 pthread_t thread = 0;*/
   /*pthread_attr_t attr;*/
   int _errno = 0;

   Info3("opening address \"%s\", rw=%d, flags=0x%x", addrs0, rw, flags);

   /* loop over retries, contains nearly the complete function */
   while (true) {

      addrs = addrs0;
      skipsp(&addrs);
      rw0 = rw;

      /* here we do not know much: will the next sub address be inter or
	 endpoint, single or dual, reverse? */

      /* check the logical direction of the current subaddress */
      reverseA = !(strncmp(addrs, xioparams->reversechar,
		       strlen(xioparams->reversechar)));
      if (reverseA) {
	 addrs += strlen(xioparams->reversechar);  /* consume "reverse" */
	 skipsp(&addrs);
      }

      if ((sfdA = xioparse_single(&addrs)) == NULL) {
	 Error1("syntax error in \"%s\"", addrs);
	 return NULL;
      }
      skipsp(&addrs);

      /* is it a dual sub address? */
      if (!strncmp(addrs, xioparams->pipesep, strlen(xioparams->pipesep))) {
	 /* yes, found dual-address operator */
	 if (rw != XIO_RDWR) {
	    Error("dual address cannot handle single direction data stream");
	 }
	 addrs += strlen(xioparams->pipesep);  /* consume "%" */
	 /* check the logical direction of the current subaddress */
	 skipsp(&addrs);
	 reverseB = !(strncmp(addrs, xioparams->reversechar,
			      strlen(xioparams->reversechar)));
	 if (reverseB) {
	    addrs += strlen(xioparams->reversechar);  /* consume "reverse" */
	    skipsp(&addrs);
	 }

	 if ((sfdB = xioparse_single(&addrs)) == NULL) {
	    Error1("syntax error in \"%s\"", addrs);
	    return NULL;
	 }
	 skipsp(&addrs);
      } else {
	 sfdB = NULL;
      }

      /* is it the final sub address? */
      if (*addrs == '\0') {
	 currentisendpoint = true;
      } else if (!strncmp(addrs, xioparams->chainsep,
			  strlen(xioparams->chainsep))) {
	 addrs += strlen(xioparams->chainsep);
	 skipsp(&addrs);
	 currentisendpoint = false;
      } else {
	 Error2("syntax error on \"%s\": expected eol or \"%s\"",
		addrs, xioparams->chainsep);
	 xiofreefd((xiofile_t *)sfdA);
	 if (sfdB != NULL)  xiofreefd((xiofile_t *)sfdB);
	 return NULL;
      }

      /* now we know the context of the current sub address:
	 currentisendpoint...it is an endpoint, not an inter address
	 sfdB.......if not null, we have a dual type address
	 reverseA...sfdA is reverse
	 reverseB...if dual address then sfdB is reverse
	 rw0......the data direction(s) of xfd0 */
      /* note: with dual inter, sfdB is implicitely reverse */

      /* calculate context parameters that are easier to handle */
      /* srch... is a bit pattern that tells which rd,wd combinations are
	 allowed for following sub address */
      if (sfdB == NULL) {
	 srchleftA  = mayleftA  = (1 << rw0);
	 srchrightA = mayrightA = (currentisendpoint ? 0 : XIOBIT_ALL);
	 if (reverseA) {
	    srchrightA = srchleftA;
	    /*srchrightA = XIOBIT_REVERSE(srchleftA); no, see what right means*/
	    srchleftA  = XIOBIT_ALL;
	 }
      } else {	/* A is only part of dual */
	 srchleftA  = mayleftA  = XIOBIT_WRONLY;
	 srchrightA = mayrightA = (currentisendpoint ? 0 : XIOBIT_RDONLY);
	 if (reverseA) {
	    srchleftA = XIOBIT_RDONLY;
	    srchrightA  = XIOBIT_WRONLY;
	 }
	 srchleftB  = mayleftB  = (currentisendpoint ? XIOBIT_RDONLY : XIOBIT_WRONLY);
	 srchrightB = mayrightB = (currentisendpoint ? 0 : XIOBIT_RDONLY);
	 if (reverseB) {
	    srchleftB  = XIOBIT_RDONLY;
	    srchrightB = XIOBIT_WRONLY;
	 }
      }

      if ((true /*0 || ((rw0+1) & (XIO_WRONLY+1))*/) || currentisendpoint) {
	 if (xioopen_unoverload(sfdA, srchleftA, &isleftA, srchrightA, &isrightA)
	     < 0) {
	    Error1("address \"%s\" can not be used in this context",
		   sfdA->addrdescs[0]->inter_desc.defname);
	 }
      } else {
	 if (xioopen_unoverload(sfdA, srchrightA, &isrightA, srchleftA, &isleftA)
	     < 0) {
	    Error1("address \"%s\" can not be used in this context",
		   sfdA->addrdescs[0]->inter_desc.defname);
	 }
      }
      if (reverseA)  {
	 int tmp;
	 tmp = isleftA;  isrightA = isleftA;  isleftA = tmp;
      }

      if (sfdB != NULL) {
	 if (xioopen_unoverload(sfdB, srchleftB, &isleftB, srchrightB, &isrightB)
	     < 0) {
	    Error1("address \"%s\" can not be used in this context",
		   sfdB->addrdescs[0]->inter_desc.defname);
	 }
	 if (reverseB)  { isleftB = XIOBIT_REVERSE(srchrightB); }
	 if (!currentisendpoint && ((isrightA+1) & (isleftB+1))) {
	    /* conflict in directions on right side (xfd1) */
	    Error("conflict in data directions");/*!!*/
	 }
	 rw1 = ((isrightA+1) | (isleftB+1)) - 1;
      } else {
	 rw1 = isrightA;
      }
      rw2 = (rw1==XIO_RDWR) ? XIO_RDWR : (rw1==XIO_RDONLY) ? XIO_WRONLY :
	 XIO_RDONLY;

      /* now we know exactly what to do with the current sub address */

      /* we need the values for retry, forever, and interval */
      applyopts_offset(sfdA, sfdA->opts);
      if (sfdB != NULL) {
	 applyopts_offset(sfdB, sfdB->opts);
      }

      /* if we found the endpoint address we are almost finished here */

      if (currentisendpoint) {
	 if (sfdB != NULL) {
	    if ((xfd0 = xioallocfd()) == NULL) {
	       xiofreefd((xiofile_t *)sfdA);  xiofreefd((xiofile_t *)sfdB);
	       return NULL;
	    }
	    xioopen_makedual(xfd0, sfdB, sfdA);
	 } else {
	    xfd0 = (xiofile_t *)sfdA;
	 }
	 /* open it and be ready in this thread */
	 if (xioopen_endpoint_dual(xfd0, rw0|flags) < 0) {
	    xiofreefd(xfd0);
	    return NULL;
	 }
	 return xfd0;
      }

      /* the current addr is not the final sub address */

      /* recursively open the following addresses of chain */
      /* loop over retries if appropriate */
      do {
	 xfd2 = socat_open(addrs, rw2, flags);
	 if (xfd2 != NULL) {
	    break;	/* succeeded */
	 }
	 if (sfdA->retry == 0 && !sfdA->forever) {
	    xiofreefd((xiofile_t *)sfdA);
	    if (sfdB != NULL)  xiofreefd((xiofile_t *)sfdB);
	    /*! close()? */
	    return NULL;
	 }
	 Nanosleep(&sfdA->intervall, NULL);
	 if (sfdA->retry)  --sfdA->retry;
      } while (true);

      /* only xfd2 is valid here, contains a handle for the rest of the chain
	 */
      /* yupp, and the single addresses sfdA and ev.sfdB are valid too, but
	 not yet opened */

      /* what are xfd0, xfd1, and xfd2?
	 consider chain: addr1|addr2
	 with no reverse address, this will run like:
	 _socat(<upstream>,addr1) --- _socat(-,   addr2) // address view
	 _socat(???,       xfd0)  --- _socat(xfd1,xfd2)  // xfd view
	 xfd0 will be opened in this routine
	 xfd1 will be assembled now, just using UNIX FDs
	 xfd2 came from recursive open call
      */
      /* but, with reverse, it looks so:
	 consider chain: ^addr1|addr2
	 _socat(<upstream>,-)      --- _socat(addr1,addr2)
	 _socat(???       ,xfd0)   --- _socat(xfd1,  xfd2)
	 xfd0 will be assembled now, just using UNIX FDs
	 xfd1 was just initialized in this routine
	 xfd2 came from recursive open call
      */
      /* even worse, with mixed forward/reverse dual address:
	 consider chain: addr1a%^addr1b|addr2
	 _socat(<upstream, addr1a%nop) --- _socat(nop%addr1b, addr2)
	 _socat(???,       xfd0)        --- _socat(xfd1,        xfd2)
      */
				
      /* prepare FD based communication of current addr with its right neighbor
	 (xfd0-xfd1) */
      {
	 switch (xioopts.pipetype) {
	 case XIOCOMM_SOCKETPAIR:
	 case XIOCOMM_SOCKETPAIRS:
	    if (xiocommpair(xioopts.pipetype,
			    (rw2+1)&(XIO_WRONLY+1), (rw1+1)&(XIO_WRONLY+1),
			    sfdB!=0, &left, &right,
			    PF_UNIX, SOCK_STREAM, 0) != 0) {
	       return NULL;
	    }
	    break;
	 case XIOCOMM_PTY:
	 case XIOCOMM_PTYS:
	    if (xiocommpair(xioopts.pipetype,
			    (rw2+1)&(XIO_WRONLY+1), (rw1+1)&(XIO_WRONLY+1),
			    sfdB!=0, &left, &right,
			    1 /* useptmx */) != 0) {
	       return NULL;
	    }
	    break;
	 default:
	    if (xiocommpair(xioopts.pipetype,
			    (rw2+1)&(XIO_WRONLY+1), (rw1+1)&(XIO_WRONLY+1),
			    sfdB!=0, &left, &right) != 0) {
	       return NULL;
	    }
	    break;
	 }
      }

      /* now assemble xfd0 and xfd1 */

      if (sfdB != NULL && reverseA == reverseB) {
	 /* dual address, differing orientation (B is impl.reverse) */
	 /* dual implies (rw0==rw1==XIO_RDWR) */
	 if (!reverseA) {

	    /* A is not reverse, but B */
#	    define MAXADDR 45
	    char addr[MAXADDR];
	    xiosingle_t *xfd0l, *xfd1r;

	    if ((xfd0 = xioallocfd()) == NULL) {
	       xiofreefd(xfd2);  return NULL;
	    }
	    snprintf(addr, MAXADDR, "FD:%u", /*0 righttoleft[0]*/left.rfd);
	    if ((xfd0l = (xiosingle_t *)socat_open(addr, XIO_WRONLY, 0))
		== NULL) {
	       xiofreefd(xfd0);  xiofreefd(xfd2);  return NULL;
	    }
	    xioopen_makedual(xfd0, xfd0l, sfdA);
	    /* address type FD keeps the FDs open per default, but ... */

	    if ((xfd1 = xioallocfd()) == NULL) {
	       xiofreefd(xfd0); xiofreefd(xfd2);  return NULL;
	    }
	    xfd1->dual.stream[1] = sfdB;
	    snprintf(addr, MAXADDR, "FD:%u", /*0 lefttoright[0]*/right.rfd);
	    if ((xfd1r = (xiosingle_t *)socat_open(addr, XIO_RDONLY, 0))
		== NULL) {
	       xiofreefd(xfd0);  xiofreefd(xfd1);  xiofreefd(xfd2);
	       return NULL;
	    }
	    xioopen_makedual(xfd1, xfd1r, sfdB);
	 } else {
	    /* A is reverse, but B is not */
	    char addr[MAXADDR];
	    xiosingle_t *xfd0r, *xfd1r;

	    if ((xfd0 = xioallocfd()) == NULL) {
	       xiofreefd(xfd2);  return NULL;
	    }
	    snprintf(addr, MAXADDR, "FD:%u", /*0 lefttoright[1]*/left.wfd);
	    if ((xfd0r = (xiosingle_t *)socat_open(addr, XIO_RDONLY, 0))
		== NULL) {
	       xiofreefd(xfd0);  xiofreefd(xfd2);	 return NULL;
	    }
	    xioopen_makedual(xfd0, sfdB, xfd0r);

	    if ((xfd1 = xioallocfd()) == NULL) {
	       xiofreefd(xfd0);  xiofreefd(xfd2);  return NULL;
	    }
	    xfd1->dual.stream[0] = sfdA;
	    snprintf(addr, MAXADDR, "FD:%u", /*0 righttoleft[1]*/right.wfd);
	    if ((xfd1r = (xiosingle_t *)socat_open(addr, XIO_RDONLY, 0))
		== NULL) {
	       xiofreefd(xfd0);  xiofreefd(xfd1);  xiofreefd(xfd2);
	       return NULL;
	    }
	    xioopen_makedual(xfd1, sfdA, xfd1r);
	 }
	 xfd0->dual.stream[0]->rfd = left.rfd;
	 xfd0->dual.stream[1]->wfd = left.wfd;
	 xfd1->dual.stream[0]->rfd = right.rfd;
	 xfd1->dual.stream[1]->wfd = right.wfd;

      } else {
	 /* either dual with equal directions, or non-dual */
	 xiofile_t *tfd;	/* temp xfd */
	 char addr[MAXADDR];
	 if (sfdB != NULL) {
	    /* dual, none or both are reverse */
	    if ((tfd = xioallocfd()) == NULL) {
	       /*! free seomething */; return NULL;
	    }
	    xioopen_makedual(tfd, sfdB, sfdA);
	 } else {
	    /* non-dual */
	    tfd = (xiofile_t *)sfdA;
	 }

	 /* now take care of orientation */
	 if (!reverseA) {
	    /* forward */
	    xfd0 = tfd;
	    if (rw1 == XIO_RDWR) {
	       snprintf(addr, MAXADDR, "FD:%u:%u", /*0 righttoleft[1]*/right.wfd, /*0 lefttoright[0]*/right.rfd);
	    } else if (rw1 == XIO_RDONLY) {
	       snprintf(addr, MAXADDR, "FD:%u", /*0 lefttoright[0]*/right.rfd);
	    } else {
	       snprintf(addr, MAXADDR, "FD:%u", /*0 righttoleft[1]*/right.wfd);
	    }
	    if ((xfd1 = socat_open(addr, rw1, 0)) == NULL) {
	       xiofreefd(xfd0);  xiofreefd(xfd2);
	       return NULL;
	    }
	 } else {
	    /* reverse */
	    xfd1 = tfd;
	    if (rw0 == XIO_RDWR) {
	       snprintf(addr, MAXADDR, "FD:%u:%u", /*0 lefttoright[1]*/left.wfd, /*0 righttoleft[0]*/left.rfd);
	    } else if (rw0 == XIO_RDONLY) {
	       snprintf(addr, MAXADDR, "FD:%u", /*0 righttoleft[0]*/left.rfd);
	    } else {
	       snprintf(addr, MAXADDR, "FD:%u", /*0 lefttoright[1]*/left.wfd);
	    }
	    if ((xfd0 = socat_open(addr, rw0/*0 XIO_RDWR*/, 0)) == NULL) {
	       xiofreefd(xfd1);  xiofreefd(xfd2);
	       return NULL;
	    }
	    /* address type FD keeps the FDs open per default, but ... */
	 }
	 if (xfd0->tag == XIO_TAG_DUAL) {
	    xfd0->dual.stream[0]->rfd = /*0 righttoleft[0]*/left.rfd;
	    xfd0->dual.stream[1]->wfd = /*0 lefttoright[1]*/left.wfd;
	 } else {
	    xfd0->stream.rfd = /*0 righttoleft[0]*/left.rfd;
	    xfd0->stream.wfd = /*0 lefttoright[1]*/left.wfd;
	 }
	 if (xfd1->tag == XIO_TAG_DUAL) {
	    xfd1->dual.stream[0]->rfd = /*0 righttoleft[0]*/left.rfd;
	    xfd1->dual.stream[1]->rfd = /*0 lefttoright[1]*/left.wfd;
	 } else {
	    xfd1->stream.rfd = /*0 lefttoright[0]*/right.rfd;
	    xfd1->stream.wfd = /*0 righttoleft[1]*/right.wfd;
	 }
      }

      /* address type FD keeps the FDs open per default, but ... */
      if (xfd0->tag == XIO_TAG_DUAL) {
	 xfd0->dual.stream[0]->howtoshut = left.howtoshut;
	 xfd0->dual.stream[0]->howtoclose = left.howtoclose;
	 xfd0->dual.stream[0]->dtype      = left.dtype;
	 xfd0->dual.stream[1]->howtoshut = right.howtoshut;
	 xfd0->dual.stream[1]->howtoclose = right.howtoclose;
	 xfd0->dual.stream[1]->dtype      = right.dtype;
      } else {
	 xfd0->stream.howtoshut = left.howtoshut;
	 xfd0->stream.howtoclose = left.howtoclose;
	 xfd0->stream.dtype      = left.dtype;
      }
      if (xfd1->tag == XIO_TAG_DUAL) {
	 xfd1->dual.stream[0]->howtoshut = left.howtoshut;
	 xfd1->dual.stream[0]->howtoclose = left.howtoclose;
	 xfd1->dual.stream[0]->dtype      = left.dtype;
	 xfd1->dual.stream[1]->howtoshut = right.howtoshut;
	 xfd1->dual.stream[1]->howtoclose = right.howtoclose;
	 xfd1->dual.stream[1]->dtype      = right.dtype;
      } else {
	 xfd1->stream.howtoshut = right.howtoshut;
	 xfd1->stream.howtoclose = right.howtoclose;
	 xfd1->stream.dtype      = right.dtype;
      }

      /* here xfd2 is valid and ready for transfer;
	 and xfd0 and xfd1 are valid and ready for opening */

      /* create a new thread that do the xioopen() of xfd1 and xfd2, and then
	 drive the transfer engine between them */
      if ((thread_arg = Malloc(sizeof(struct threadarg_struct))) == NULL) {
	 /*! free something */
	 xiofreefd(xfd0); xiofreefd(xfd1); xiofreefd(xfd2);
	 return NULL;
      }
      thread_arg->rw = (reverseA ? rw1 : rw0);
      thread_arg->xfd1 = xfd1;
      thread_arg->xfd2 = xfd2;
      Notice5("starting thread: dir=%d, reverseA=%d, reverseB=%d, xfd1->tag=%d, xfd2->tag=%d",
	      rw0, reverseA, reverseB, xfd1->tag, xfd2->tag);
      if (xfd1->tag==XIO_TAG_DUAL) {
	 Notice4("xfd1: [%s, wfd=%d] %% [%s, rfd=%d]",
		 xfd1->dual.stream[1]->addrdesc->common_desc.defname,
		 xfd1->dual.stream[1]->wfd,
		 xfd1->dual.stream[0]->addrdesc->common_desc.defname,
		 xfd1->dual.stream[0]->rfd);
      } else {
	 Notice3("xfd1: %s, wfd=%d, rfd=%d",
		 xfd1->stream.addrdesc->common_desc.defname,
		 xfd1->stream.wfd, xfd1->stream.rfd);
      }
      if (xfd2->tag==XIO_TAG_DUAL) {
	 Notice4("xfd2: [%s, wfd=%d] %% [%s, rfd=%d]",
		 xfd2->dual.stream[1]->addrdesc->common_desc.defname,
		 xfd2->dual.stream[1]->wfd,
		 xfd2->dual.stream[0]->addrdesc->common_desc.defname,
		 xfd2->dual.stream[0]->rfd);
      } else {
	 Notice3("xfd2: %s, wfd=%d, rfd=%d",
		 xfd2->stream.addrdesc->common_desc.defname,
		 xfd2->stream.wfd, xfd2->stream.rfd);
      }
      Info5("pthread_create(%p, NULL, %s, {%d,%p,%p})",
	    &xfd0->stream.subthread,
	    (reverseA||(sfdB!=NULL)&&!reverseB)?"xioopenleftthenengine":"xioengine",
	    thread_arg->rw, thread_arg->xfd1, thread_arg->xfd2);
      if ((_errno =
	   Pthread_create(&xfd0->stream.subthread, NULL,
			  (reverseA||(sfdB!=NULL)&&!reverseB)?xioopenleftthenengine:xioengine,
			   thread_arg))
	  != 0) {
	 Error3("pthread_create(%p, {}, xioengine, %p): %s",
		&xfd0->stream.subthread, thread_arg,
		strerror(_errno));
	 xiofreefd(xfd0); xiofreefd(xfd1); xiofreefd(xfd2);
	 free(thread_arg); return NULL;
      }
      Info1("started thread "F_thread, xfd0->stream.subthread);
      xfd1 = NULL;
      xfd2 = NULL;

      /* open protocol part */
      if (xioopen_inter_dual(xfd0, rw|flags)
	  < 0) {
	 /*! close sub chain */
	 if (xfd0->stream.retry == 0 && !xfd0->stream.forever) {
	    xiofreefd(xfd0); /*! close()? */ return NULL;
	 }
	 Nanosleep(&xfd0->stream.intervall, NULL);
	 if (xfd0->stream.retry)  --xfd0->stream.retry;
	 continue;
      }
      break;
   }
   /*!!!?*/
#if 0
   xfd0->stream.howtoshut = XIOSHUT_CLOSE;
#endif
   return xfd0;
}

void *xioopenleftthenengine(void *thread_void) {
   struct threadarg_struct *thread_arg = thread_void;
   int rw = thread_arg->rw;
   xiofile_t *xfd1 = thread_arg->xfd1;
   xiofile_t *xfd2 = thread_arg->xfd2;

   /*! design a function with better interface */
   if (xioopen_inter_dual(xfd1, rw|XIO_MAYCONVERT|XIO_MAYCHILD) < 0) {
      xioclose(xfd2);
      xiofreefd(xfd1);
      xiofreefd(xfd2);
      return NULL;
   }
   xioengine(thread_void);
   return NULL;
}


#if 0 /* not used in socat V2 */
xiofile_t *xioparse_dual(const char **addr) {
   xiofile_t *xfd;
   xiosingle_t *sfd1;
   int reverse;

   /* check the logical direction of the current subaddress */
   reverse = !(strncmp(*addr, xioparams->reversechar,
		       strlen(xioparams->reversechar)));
   if (reverse) {
      /* skip "reverse" token */
      *addr += strlen(xioparams->reversechar);
   }

   /* we parse a single address */
   if ((sfd1 = xioparse_single(addr)) == NULL) {
      return NULL;
   }
   sfd1->reverse = reverse;

   /* and now we see if we reached a dual-address separator */
   if (strncmp(*addr, xioopts.pipesep, strlen(xioopts.pipesep))) {
      /* no, finish */
      return (xiofile_t *)sfd1;
   }

   /* we found the dual-address operator, so we parse the second single address
      */
   *addr += strlen(xioparams->pipesep);

   if ((xfd = xioallocfd()) == NULL) {
      xiofreefd((xiofile_t *)sfd1);
      return NULL;
   }
   xfd->tag = XIO_TAG_DUAL;
   xfd->dual.stream[0] = sfd1;
   if ((xfd->dual.stream[1] = xioparse_single(addr)) == NULL) {
      xiofreefd(xfd); /*! and maybe have free some if its contents */
      return NULL;
   }
   
   return xfd;
}
#endif /* not used in socat V2 */

int xioopen_inter_dual(xiofile_t *xfd, int xioflags) {

   if (xfd->tag == XIO_TAG_DUAL) {
      /* a really dual address */
      if ((xioflags&XIO_ACCMODE) != XIO_RDWR) {
	 Warn("unidirectional open of dual address");
      }

      /* a "usual" bidirectional stream specification, one address */
      if (((xioflags&XIO_ACCMODE)+1) & (XIO_RDONLY+1)) {
	 if (xioopen_inter_single((xiofile_t *)xfd->dual.stream[0], XIO_RDONLY|(xioflags&~XIO_ACCMODE&~XIO_MAYEXEC))
	     < 0) {
	    return -1;
	 }
      }
      /*! should come before xioopensingle? */
      if (((xioflags&XIO_ACCMODE)+1) & (XIO_WRONLY+1)) {
	 if (xioopen_inter_single((xiofile_t *)xfd->dual.stream[1], XIO_WRONLY|(xioflags&~XIO_ACCMODE&~XIO_MAYEXEC))
	     < 0) {
	    xioclose((xiofile_t *)xfd->dual.stream[0]);
	    return -1;
	 }
      }
      return 0;
   }

   return xioopen_inter_single(xfd, xioflags);
}

int xioopen_endpoint_dual(xiofile_t *xfd, int xioflags) {

   if (xfd->tag == XIO_TAG_DUAL) {
      /* a really dual address */
      if ((xioflags&XIO_ACCMODE) != XIO_RDWR) {
	 Warn("unidirectional open of dual address");
      }

      /* a "usual" bidirectional stream specification, one address */
      if (((xioflags&XIO_ACCMODE)+1) & (XIO_WRONLY+1)) {
	 if (xioopen_endpoint_single((xiofile_t *)xfd->dual.stream[1], XIO_WRONLY|(xioflags&~XIO_ACCMODE&~XIO_MAYEXEC))
	     < 0) {
	    return -1;
	 }
      }
      /*! should come before xioopensingle? */
      if (((xioflags&XIO_ACCMODE)+1) & (XIO_RDONLY+1)) {
	 if (xioopen_endpoint_single((xiofile_t *)xfd->dual.stream[0], XIO_RDONLY|(xioflags&~XIO_ACCMODE&~XIO_MAYEXEC))
	     < 0) {
	    xioclose((xiofile_t *)xfd->dual.stream[1]);
	    return -1;
	 }
      }
      return 0;
   }

   return xioopen_endpoint_single(xfd, xioflags);
}


/* parses the parameters and options of a single (sub)address.
   returns 0 on success or -1 if an error occurred. */
static xiosingle_t *
   xioparse_single(const char **addr	/* input string; afterwards points to
					   first char not belonging to this
					   sub address */
		   ) {
   char addr0[20];
   xiofile_t *xfd;
   xiosingle_t *sfd;
   struct xioaddrname *ae;
   /*int maxparams;*/	/* max number of parameters */
   const char *ends[4+1];
   const char *hquotes[] = {
      "'",
      NULL
   } ;
   const char *squotes[] = {
      "\"",
      NULL
   } ;
   const char *nests[] = {
      "'", "'",
      "(", ")",
      "[", "]",
      "{", "}",
      NULL
   } ;
   char token[512], *tokp;
   size_t len;
   int i;

   /* init */
   i = 0;
   ends[i++] = xioopts.chainsep;	/* default: "|" */
   ends[i++] = xioopts.pipesep;		/* default: "%" */
   ends[i++] = ","/*xioopts.comma*/;		/* default: "," */
   ends[i++] = ":"/*xioopts.colon*/;		/* default: ":" */
   ends[i++] = NULL;

   if ((xfd = xioallocfd()) == NULL) {
      return NULL;
   }
   sfd = &xfd->stream;
   sfd->argc = 0;

   /* for error messages */
   addr0[0] = '\0'; strncat(addr0, *addr, sizeof(addr0)-1);

   len = sizeof(token); tokp = token;
   if (nestlex(addr, &tokp, &len, ends, hquotes, squotes, nests,
	       true, true, true, false) < 0) {
      Error2("keyword too long, in address \"%s%s\"", token, *addr);
   }
   *tokp = '\0';  /*! len? */
   ae = (struct xioaddrname *)
      keyw((struct wordent *)&address_names, token,
	   sizeof(address_names)/sizeof(struct xioaddrname)-1);
   if (ae != NULL) {
      /* found keyword */
      sfd->addrdescs = ae->desc;
      if ((sfd->argv[sfd->argc++] = strdup(token)) == NULL) {
	 Error1("strdup(\"%s\"): out of memory", token);
      }
   } else {
      if (false) {	/* for canonical reasons */
	 ;
#if WITH_FDNUM
      } else if (isdigit(token[0]&0xff) && token[1] == '\0') {
	 Info1("interpreting address \"%s\" as file descriptor", token);
	 sfd->addrdescs = xioaddrs_fdnum;
	 if ((sfd->argv[sfd->argc++] = strdup("FD")) == NULL) {
	    Error("strdup(\"FD\"): out of memory");
	 }
	 if ((sfd->argv[sfd->argc++] = strdup(token)) == NULL) {
	    Error1("strdup(\"%s\"): out of memory", token);
	 }
	 /*! check argc overflow */
#endif /* WITH_FDNUM */
#if WITH_GOPEN
      } else if (strchr(token, '/')) {
	 Info1("interpreting address \"%s\" as file name", token);
	 sfd->addrdescs = xioaddrs_gopen;
	 if ((sfd->argv[sfd->argc++] = strdup("GOPEN")) == NULL) {
	    Error("strdup(\"GOPEN\"): out of memory");
	 }
	 if ((sfd->argv[sfd->argc++] = strdup(token)) == NULL) {
	    Error1("strdup(\"%s\"): out of memory", token);
	 }
	 /*! check argc overflow */
#endif /* WITH_GOPEN */
      } else {
	 Error1("unknown device/address \"%s\"", token);
	 xiofreefd(xfd);
	 return NULL;
      }
   }

   while (!strncmp(*addr, xioopts.paramsep, strlen(xioopts.paramsep))) {
      if (sfd->argc >= MAXARGV) {
	 Error1("address \"%s\": succeeds max number of parameters",
		sfd->argv[0]);
      }
      *addr += strlen(xioopts.paramsep);
      len = sizeof(token);  tokp = token;
      if (nestlex(addr, &tokp, &len, ends, hquotes, squotes, nests,
		  true, true, true, false) != 0) {
	 Error2("syntax error in address \"%s%s\"", token, *addr);
      }
      *tokp = '\0';
      if ((sfd->argv[sfd->argc++] = strdup(token)) == NULL) {
	 Error1("strdup(\"%s\"): out of memory", token);
      }
   }

   if (parseopts(addr, &sfd->opts) < 0) {
      xiofreefd(xfd);
      return NULL;
   }

   return sfd;
}

/* during parsing the sub address, we did not know if it is used as inter
   address or endpoint; therefore we could not select the appropriate address
   descriptor. Here we already know the context and thus select the address
   descriptor and check the option groups.
   returns 0 on success or -1 if an error occurred */
static int
   xioopen_unoverload(xiosingle_t *sfd,
		      int mayleft,	/* what may be on left side: or'd
					   XIOBIT_RDWR, XIOBIT_RDONLY,
					   XIOBIT_WRONLY */
		      int *isleft,	/* what the selected address desc
					   provides on left side: XIO_RDWR,
					   XIO_RDONLY, or XIO_WRONLY */
		      int mayright,	/* what may be on right side: or'd
					   XIOBIT_RDWR, XIOBIT_RDONLY,
					   XIOBIT_WRONLY */
		      int *isright)	/* what the selected address desc
					   provides on right side: XIO_RDWR,
					   XIO_RDONLY, or XIO_WRONLY */
{
   const union xioaddr_desc **addrdescs;
   int tag;
   int i;

   addrdescs = sfd->addrdescs;
   tag = (mayright ? XIOADDR_INTER : XIOADDR_ENDPOINT);

   /* look for a matching entry in the list of address descriptions */
   Debug5("searching record for \"%s\" with tag=%d, numparams=%d, leftdir %d, rightdir %d",
	  addrdescs[0]->common_desc.defname,
	  tag, sfd->argc-1, mayleft, mayright);
   while ((*addrdescs) != NULL) {
      if ((*addrdescs)->tag == tag &&
	  addrdescs[0]->common_desc.numparams == sfd->argc-1 &&
	  (addrdescs[0]->common_desc.leftdirs & mayleft) != 0 &&
	  (mayright ? (addrdescs[0]->inter_desc.rightdirs & mayright) : 1)) {
	 break;
      }
      ++addrdescs;
   }

   if (addrdescs[0] == NULL) {
      if (tag == XIOADDR_ENDPOINT) {
	 Error3("address \"%s...\" in endpoint context, leftdirs=%d, with %d parameter(s) is not available",
		sfd->argv[0], mayleft, sfd->argc-1);
      } else {
	 Error4("address \"%s...\" in intermediate context, leftdirs=%d, rightdirs=%d, with %d parameter(s) is not available",
		sfd->argv[0], mayleft, mayright, sfd->argc-1);
      }
      xiofreefd((xiofile_t *)sfd);  return -1;
   }

   i = (addrdescs[0]->common_desc.leftdirs & mayleft);
   *isleft = 0;
   while (i>>=1) {
      ++*isleft;
   }
   if (true /*0 mayright*/) {
      i = (addrdescs[0]->inter_desc.rightdirs & mayright);
      *isright = 0;
      while (i>>=1) {
	 ++*isright;
      }
   }
   sfd->tag  = (*isleft + 1);
   sfd->addrdesc = addrdescs[0];
   if (addrdescs[0]->common_desc.howtoshut != XIOSHUT_UNSPEC)
      sfd->howtoshut = addrdescs[0]->common_desc.howtoshut;
   if (addrdescs[0]->common_desc.howtoclose != XIOCLOSE_UNSPEC)
      sfd->howtoclose  = addrdescs[0]->common_desc.howtoclose;

   if (tag == XIOADDR_ENDPOINT) {
      Debug1("selected record with leftdirs %d",
	     addrdescs[0]->common_desc.leftdirs);
   } else {
      Debug2("selected record with leftdirs %d, rightdirs %d",
	     addrdescs[0]->common_desc.leftdirs,
	     addrdescs[0]->inter_desc.rightdirs);
   }
   return 0;
}


static int
   xioopen_inter_single(xiofile_t *xfd, int xioflags) {
   const struct xioaddr_inter_desc *addrdesc;
   int result;

   addrdesc = &xfd->stream.addrdesc->inter_desc;

   if ((xioflags&XIO_ACCMODE) == XIO_RDONLY) {
      xfd->tag = XIO_TAG_RDONLY;
   } else if ((xioflags&XIO_ACCMODE) == XIO_WRONLY) {
      xfd->tag = XIO_TAG_WRONLY;
   } else if ((xioflags&XIO_ACCMODE) == XIO_RDWR) {
      xfd->tag = XIO_TAG_RDWR;
   } else {
      Error1("invalid mode for address \"%s\"", xfd->stream.argv[0]);
   }
   xfd->stream.flags     &= (~XIO_ACCMODE);
   xfd->stream.flags     |= (xioflags & XIO_ACCMODE);
   result = (*addrdesc->func)(xfd->stream.argc, xfd->stream.argv,
			      xfd->stream.opts, xioflags, xfd, 
			      addrdesc->groups, addrdesc->arg1,
			      addrdesc->arg2, addrdesc->arg3);
   return result;
}

static int
   xioopen_endpoint_single(xiofile_t *xfd, int xioflags) {
   const struct xioaddr_endpoint_desc *addrdesc;
   int result;

   addrdesc = &xfd->stream.addrdesc->endpoint_desc;
   if ((xioflags&XIO_ACCMODE) == XIO_RDONLY) {
      xfd->tag = XIO_TAG_RDONLY;
   } else if ((xioflags&XIO_ACCMODE) == XIO_WRONLY) {
      xfd->tag = XIO_TAG_WRONLY;
   } else if ((xioflags&XIO_ACCMODE) == XIO_RDWR) {
      xfd->tag = XIO_TAG_RDWR;
   } else {
      Error1("invalid mode for address \"%s\"", xfd->stream.argv[0]);
   }
   xfd->stream.flags     &= (~XIO_ACCMODE);
   xfd->stream.flags     |= (xioflags & XIO_ACCMODE);
   result = (*addrdesc->func)(xfd->stream.argc, xfd->stream.argv,
			      xfd->stream.opts, xioflags, xfd, 
			      addrdesc->groups, addrdesc->arg1,
			      addrdesc->arg2, addrdesc->arg3);
   return result;
}
