/* source: xioshutdown.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 of the extended shutdown function */


#include "xiosysincludes.h"
#include "xioopen.h"


static int xioshut_sleep_kill(pid_t sub, unsigned long usec, int sig);

static pid_t socat_kill_pid;	/* here we pass the pid to be killed in sighandler */

static void signal_kill_pid(int dummy) {
   int _errno;
   _errno = errno;
   diag_in_handler = 1;
   Notice("SIGALRM while waiting for w/o child process to die, killing it now");
   Kill(socat_kill_pid, SIGTERM);
   diag_in_handler = 0;
   errno = _errno;
}

/* how: SHUT_RD, SHUT_WR, or SHUT_RDWR */
int xioshutdown(xiofile_t *sock, int how) {
   int fd;
   int result = 0;

   Debug2("xioshutdown(%p, %d)", sock, how);
   Debug2("xioshutdown(): dtype=0x%x, howtoshut=0x%04x",
	  sock->stream.dtype, sock->stream.howtoshut);

   if (sock->tag == XIO_TAG_INVALID) {
      Error("xioshutdown(): invalid file descriptor");
      errno = EINVAL;
      return -1;
   }

   /*Debug3("xioshutdown: flags=%d, dtype=%d, howtoclose=%d", sock->stream.flags, sock->stream.dtype, sock->stream.howtoclose);*/
   if (sock->tag == XIO_TAG_DUAL) {
      if ((how+1)&1) {
	 result = xioshutdown((xiofile_t *)sock->dual.stream[0], 0);
      }
      if ((how+1)&2) {
	 result |= xioshutdown((xiofile_t *)sock->dual.stream[1], 1);
      }
      return result;
   }

   fd = XIO_GETWRFD(sock);

   /* let us bring how nearer to the resulting action */
   if ((sock->stream.flags&XIO_ACCMODE) == XIO_WRONLY) {
      how = ((how+1) & ~(SHUT_RD+1)) - 1;
   } else if ((sock->stream.flags&XIO_ACCMODE) == XIO_RDONLY) {
      how = ((how+1) & ~(SHUT_WR+1)) - 1;
   }

   switch (sock->stream.howtoshut) {
#if WITH_PTY
   case XIOSHUT_PTYEOF:
      {
	 struct termios termarg;
	 int result;
	 Debug1("tcdrain(%d)", sock->stream.wfd);
	 result = tcdrain(sock->stream.wfd);
	 Debug1("tcdrain() -> %d", result);
	 if (Tcgetattr(sock->stream.wfd, &termarg) < 0) {
	    Error3("tcgetattr(%d, %p): %s",
		   sock->stream.wfd, &termarg, strerror(errno));
	 }
#if 0
	 /* these settings might apply to data still in the buff (despite the
	    TCSADRAIN */
	 termarg.c_iflag |= (IGNBRK | BRKINT | PARMRK | ISTRIP
                           | INLCR | IGNCR | ICRNL | IXON);
	 termarg.c_oflag |= OPOST;
	 termarg.c_lflag |= (/*ECHO | ECHONL |*/ ICANON | ISIG | IEXTEN);
	 //termarg.c_cflag |= (PARENB);
#else
	 termarg.c_lflag |= ICANON;
#endif
	 if (Tcsetattr(sock->stream.wfd, TCSADRAIN, &termarg) < 0) {
	    Error3("tcsetattr(%d, TCSADRAIN, %p): %s",
		   sock->stream.wfd, &termarg, strerror(errno));
	 }
	 if (Write(sock->stream.wfd, &termarg.c_cc[VEOF], 1) < 1) {
	    Warn3("write(%d, 0%o, 1): %s",
		  sock->stream.wfd, termarg.c_cc[VEOF], strerror(errno));
	 }
      }
      return 0;
#endif /* WITH_PTY */
#if WITH_OPENSSL
   case XIOSHUT_OPENSSL:
      sycSSL_shutdown(sock->stream.para.openssl.ssl);
      /*! what about half/full close? */
      return 0;
#endif /* WITH_OPENSSL */
   default:
      break;
   }

   /* here handle special shutdown functions */
   switch (sock->stream.howtoshut & XIOSHUTWR_MASK) {
      char writenull;
   case XIOSHUTWR_NONE:
      return 0;
   case XIOSHUTWR_CLOSE:
      if (Close(fd) < 0) {
	 Info2("close(%d): %s", fd, strerror(errno));
      }
      return 0;
   case XIOSHUTWR_DOWN:
      if ((result = Shutdown(fd, how)) < 0) {
	 Info3("shutdown(%d, %d): %s", fd, how, strerror(errno));
      }
      return 0;
#if _WITH_SOCKET
   case XIOSHUTWR_NULL:
      /* send an empty packet; only useful on datagram sockets? */
      xiowrite(sock, &writenull, 0);
      return 0;
#endif /* _WITH_SOCKET */
   default: break;
   }

#if 0
  if (how == SHUT_RDWR) {
     /* in this branch we handle only shutdown actions where read and write
	shutdown are not independent */

   switch (sock->stream.howtoshut) {
#if _WITH_SOCKET
     case XIOSHUT_DOWN:
      if ((result = Shutdown(fd, how)) < 0) {
	 Info3("shutdown(%d, %d): %s", fd, how, strerror(errno));
      }
      break;
     case XIOSHUT_KILL:
	if ((result = Shutdown(fd, how)) < 0) {
	 Info3("shutdown(%d, %d): %s", fd, how, strerror(errno));
      }
      break;
#endif /* _WITH_SOCKET */
     case XIOSHUT_CLOSE:
	Close(fd);
#if WITH_TERMIOS
	if (sock->stream.ttyvalid) {
	   if (Tcsetattr(fd, 0, &sock->stream.savetty) < 0) {
	      Warn2("cannot restore terminal settings on fd %d: %s",
		    fd, strerror(errno));
	   }
	}
#endif /* WITH_TERMIOS */
	/*PASSTHROUGH*/
     case XIOSHUT_NONE:
	break;
     default:
	Error1("xioshutdown(): bad shutdown action 0x%x", sock->stream.howtoshut);
	return -1;
     }

#if 0 && _WITH_SOCKET
   case XIODATA_RECVFROM:
      if (how >= 1) {
	 if (Close(fd) < 0) {
	    Info2("close(%d): %s",
		  fd, strerror(errno));
	 }
	 sock->stream.eof = 2;
	 sock->stream.rfd = -1;
      }
      break;
#endif /* _WITH_SOCKET */
  }
#endif

   if ((how+1) & 1) {	/* contains SHUT_RD */
      switch (sock->stream.dtype & XIODATA_READMASK) {
	 /* shutdown read channel */

      case XIOREAD_STREAM:
      case XIODATA_2PIPE:
	 if (Close(fd) < 0) {
	    Info2("close(%d): %s", fd, strerror(errno));
	 }
	 break;
      }
   }

   if ((how+1) & 2) {	/* contains SHUT_WR */
      /* shutdown write channel */

      switch (sock->stream.howtoshut & XIOSHUTWR_MASK) {

      case XIOSHUTWR_CLOSE:
	 if (Close(fd) < 0) {
	    Info2("close(%d): %s", fd, strerror(errno));
	 }
	 /*PASSTHROUGH*/
      case XIOSHUTWR_NONE:
	 break;

#if _WITH_SOCKET
      case XIOSHUTWR_DOWN:
	 if (Shutdown(fd, SHUT_WR) < 0) {
	    Info2("shutdown(%d, SHUT_WR): %s", fd, strerror(errno));
	 }
	 break;
#endif /* _WITH_SOCKET */

#if 0
      case XIOSHUTWR_DOWN_KILL:
	 if (Shutdown(fd, SHUT_WR) < 0) {
	    Info2("shutdown(%d, SHUT_WR): %s", fd, strerror(errno));
	 }
	 /*!!!*/
#endif
      case XIOSHUTWR_SIGHUP:
	 /* the child process might want to flush some data before
	    terminating */
	 xioshut_sleep_kill(sock->stream.child.pid, 0, SIGHUP);
	 break;
      case XIOSHUTWR_SIGTERM:
	 /* the child process might want to flush some data before
	    terminating */
	 xioshut_sleep_kill(sock->stream.child.pid, 1000000, SIGTERM);
	 break;
      case XIOSHUTWR_SIGKILL:
	 /* the child process might want to flush some data before
	    terminating */
	 xioshut_sleep_kill(sock->stream.child.pid, 1000000, SIGKILL);
	 break;

      default:
	 Error1("xioshutdown(): unhandled howtoshut=0x%x during SHUT_WR",
		sock->stream.howtoshut&XIOSHUTWR_MASK);
      }
      sock->stream.wfd = -1;
   }

   return result;
}

/* wait some time and then send signal to sub process. This is useful after
   shutting down the connection to give process some time to flush its output
   data */
static int xioshut_sleep_kill(pid_t sub, unsigned long usec, int sig) {
   struct sigaction act;
   int status = 0;

   /* we wait for the child process to die, but to prevent timeout
      we raise an alarm after some time.
      NOTE: the alarm does not terminate waitpid() on Linux/glibc
      (BUG?), 
      therefore we have to do the kill in the signal handler */
	 {
	    struct sigaction act;
	    sigfillset(&act.sa_mask);
	    act.sa_flags = 0;
	    act.sa_handler = signal_kill_pid;
	    Sigaction(SIGALRM, &act, NULL);
	 }

   socat_kill_pid = sub;
#if HAVE_SETITIMER
   /*! with next feature release, we get usec resolution and an option
	      */
#else
   Alarm(1 /*! sock->stream.child.waitdie */);
#endif /* !HAVE_SETITIMER */
   if (Waitpid(sub, &status, 0) < 0) {
      Warn3("waitpid("F_pid", %p, 0): %s",
	    sub, &status, strerror(errno));
   }
   Alarm(0);
   return 0;
}
