/*///---------------------------------------------------------------------\\\*/
/*// Copyright (c)               (_)/=\~/_(_)         All rights reserved. \\*/
/*/  2005-2006                (_)  /=\  ~/_  (_)        Released under the  \*/
/*\  Jim Peters            (_)    /=\    ~/_    (_)             GNU GPL v2  /*/
/*\\ http://uazu.net    (_) ____ /=\ ____ ~/_ ____ (_)      http://fsf.org //*/
/*\\\---------------------------------------------------------------------///*/

//
//	tupipe: two-way pipe
//
//	Connect on to another command with bidirectional pipes to/from
//	STDIN/STDOUT, upstream and downstream.  Optionally restricts
//	bandwidth to a kbps value (e.g. 56 for modem emulation, 256 or
//	512 for broadband).  Also, optionally simulates an error
//	condition after a given number of input or output \n's,
//	according to the options.  Displays any signals received, to
//	aid debugging.
//

#include <stdio.h>
#include <sys/poll.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdarg.h>
#include <errno.h>

void
error(char *fmt, ...) {
   va_list ap;
   va_start(ap, fmt);
   fprintf(stderr, "tupipe: ");
   vfprintf(stderr, fmt, ap);
   fprintf(stderr, "\n");
   exit(1);
}

void
warn(char *fmt, ...) {
   va_list ap;
   va_start(ap, fmt);
   fprintf(stderr, "tupipe: ");
   vfprintf(stderr, fmt, ap);
   fprintf(stderr, "\n");
}

#define NL "\n"
void 
usage() {
   error("Usage: tupipe [options] command args..."
	 NL "Options:"
	 NL "  -k <speed>    # Limit bandwidth both ways to <speed> kbps (e.g. 56 or 512)"
	 NL "  -o <cnt>      # Trigger action (-D/-B/-X) after <cnt> output -> lines"
	 NL "  -i <cnt>      # Trigger action (-D/-B/-X) after <cnt> input <- lines"
	 NL "  -D            # Drop all data in -i/-o direction; read it but don't pass it on"
	 NL "  -B            # Block all data in -i/-o direction, i.e. refuse to read any "
	 NL "                #   more data, but stay alive"
	 NL "  -X            # Exit, die completely"
	 NL "  -d            # Dump all in/out lines to stderr, for debugging"
	 );
}

void
die(char *fil, int lin) {
   error("ABORT: unexpected condition at %s line %d", fil, lin);
}
#define DIE die(__FILE__, __LINE__)


// Signals I'm going to catch and report
int siglist[]= {
   SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT, SIGFPE, SIGSEGV, 
   SIGPIPE, SIGALRM, SIGTERM, SIGUSR1, SIGUSR2, SIGCONT, 
   SIGTSTP, SIGTTIN, SIGTTOU, 0
};
char *signame[]= {
   "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGABRT", "SIGFPE", "SIGSEGV", 
   "SIGPIPE", "SIGALRM", "SIGTERM", "SIGUSR1", "SIGUSR2", "SIGCONT",
   "SIGTSTP", "SIGTTIN", "SIGTTOU", 0
};


	 
// Globals
	 
int otrig= -1;
int itrig= -1;
int o_drop;
int o_block;
int o_exit;
int o_kbps;
int o_dump;
int zexit;		// Exit triggered?
int zidrop;		// Input drop triggered?
int ziblock;		// Input block triggered?
int zodrop;		// Output drop triggered?
int zoblock;		// Output block triggered?
pid_t rem_pid= 0;
int terminate= 0;
int terminate_rv;

static void
sigchld_handler(int sig) {
   pid_t pid;
   int status;

   if (!rem_pid) return;

   pid= waitpid(rem_pid, &status, WNOHANG);
   if (pid == rem_pid) {
      terminate_rv= 1;
      if (!WIFEXITED(status))
         warn("Sub-command exited abnormally");
      else 
	 warn("Sub-command exited with code: %d", terminate_rv= WEXITSTATUS(status));
      terminate= 1;
      return;        // Return without setting up another handler
   }

   // Setup handler again
   if (SIG_ERR == signal(SIGCHLD, sigchld_handler))
      error("Can't set SIGCHLD signal handler");
}

static void
signal_handler(int sig) {
   int a;
   
   for (a= 0; siglist[a] && siglist[a] != sig; a++) ;
   if (!siglist[a]) DIE;

   warn("Signal %s received and ignored", signame[a]);

   // Setup handler again
   if (SIG_ERR == signal(sig, signal_handler))
      error("Can't reset %s signal handler", signame[a]);
}

// Dump in/out lines for debugging
void 
dump(char *pre, char *dat, int len) {
   int nl= 1;
   char ch;
   while (len-- > 0) {
      if (nl) { fprintf(stderr, "tupipe: %s ", pre); nl= 0; }
      ch= *dat++;
      if (ch != '\n' && ch != '\t' && (ch < 32 || ch > 126)) 
	 fprintf(stderr, "\\x%02x", ch&255);
      else 
	 fputc(ch, stderr);
      if (ch == '\n') nl= 1;
   }
   if (!nl) fputc('\n', stderr);
}
      

// Get time, micro-seconds % 2^32
int 
time_us() {
   struct timeval tv;
   gettimeofday(&tv, NULL);
   return tv.tv_sec * 1000000 + tv.tv_usec;
}


// Main

int 
main(int ac, char **av) {
   int pout[2], pin[2];
   int a, in, out;
   pid_t pid;
   
   ac--, av++;

   if (ac <= 0) usage();
   
   while (ac > 0 && av[0][0] == '-') {
      char ch, *p= &av[0][1]; ac--, av++;
      while ((ch= *p++)) switch (ch) {
       case 'k':
	  if (ac-- < 1 || 1 != sscanf(*av++, "%d %c", &o_kbps, &ch)) usage();
	  if (o_kbps <= 0) error("Bad kbps value: %d", o_kbps);
	  break;
       case 'o':
	  if (ac-- < 1 || 1 != sscanf(*av++, "%d %c", &otrig, &ch)) usage();
	  break;
       case 'i':
	  if (ac-- < 1 || 1 != sscanf(*av++, "%d %c", &itrig, &ch)) usage();
	  break;
       case 'B':
	  o_block= 1; break;
       case 'D':
	  o_drop= 1; break;
       case 'X':
	  o_exit= 1; break;
       case 'd':
	  o_dump= 1; break;
       default:
	  usage();
      }
   }

   if (0 != pipe(pout) || 0 != pipe(pin)) DIE;
   
   in= pin[0];
   out= pout[1];
   
   pid= fork();
   if (pid < 0) error("Can't fork()");
   if (pid == 0) {
      // Child: switch stdin/out to pipes, run command
      dup2(pout[0], 0);
      dup2(pin[1], 1);
      execvp(av[0], av);
      error("Can't execute remote symirr via '%s'", av[0]);
   }

   rem_pid= pid;
   
   if (SIG_ERR == signal(SIGCHLD, sigchld_handler))
      error("Can't set SIGCHLD signal handler");

   for (a= 0; siglist[a]; a++)
      if (SIG_ERR == signal(siglist[a], signal_handler))
	 error("Can't set %s signal handler", signame[a]);
   
   // There is a chance that the sub-process has already died.  In
   // which case we need to pick it up with a wait() call right away.
   sigchld_handler(0);

   {
      char obuf[4096], ibuf[4096];
      int ob_len= 0;
      int ib_len= 0;
      int sz_buf;
      struct pollfd pfd[4];
      int ign[4];
      int rv;
      int owait= 0;	// Wait until this us time, else 0
      int iwait= 0;	// Wait until this us time, else 0
      int delay;
      int now;
      char *chnam[]= { "-> tupipe", "<- tupipe", "tupipe <-", "tupipe ->" };

      memset(ign, 0, sizeof(ign));

      // Adjust size of buffer to get about 10 blocks/sec at kbps rate, if set
      sz_buf= sizeof(obuf);
      if (o_kbps) sz_buf= o_kbps*1000/8/10;
      if (sz_buf < 64) sz_buf= 64;

      now= time_us();
      while (1) {
	 if (zexit && (ob_len == 0 && ib_len == 0)) error("Exit triggered");
	 if (terminate && ib_len == 0) exit(terminate_rv);

	 pfd[0].fd= 0;
	 pfd[0].events= (ob_len || zoblock || owait ? 0 : POLLIN) | POLLERR | POLLHUP;
	 pfd[1].fd= 1;
	 pfd[1].events= (ib_len ? POLLOUT : 0) | POLLERR | POLLHUP;
	 pfd[2].fd= in;
	 pfd[2].events= (ib_len || ziblock || iwait ? 0 : POLLIN) | POLLERR | POLLHUP;
	 pfd[3].fd= out;
	 pfd[3].events= (ob_len ? POLLOUT : 0) | POLLERR | POLLHUP;

	 for (a= 0; a<4; a++) pfd[a].events &= ~ign[a];

	 delay= 1000;
	 if (owait && 1+(owait-now)/1000 < delay) delay= 1+(owait-now)/1000;
	 if (iwait && 1+(iwait-now)/1000 < delay) delay= 1+(iwait-now)/1000;
	 //warn("Delay %4d  iwait %08x  owait %08x  now %08x", delay, iwait, owait, now);
	 rv= poll(&pfd[0], 4, delay);
	 if (rv < 0 && errno != EINTR) 
	    error("poll() failed (%s)", strerror(errno));
	 if (terminate && ib_len == 0) exit(1);

	 now= time_us();
	 if (owait && owait - now < 0) owait= 0;
	 if (iwait && iwait - now < 0) iwait= 0;
	 if (rv <= 0) continue;

	 for (a= 0; a<4; a++) {
	    int re= pfd[a].revents & ~ign[a];
	    if (re & POLLERR) { ign[a] |= POLLERR; warn("Error condition on channel: %s", chnam[a]); }
	    if (re & POLLHUP) { ign[a] |= POLLHUP; warn("Hangup condition on channel: %s", chnam[a]); }
	    if (re & POLLNVAL) { ign[a] |= POLLNVAL; warn("Invalid condition on channel: %s", chnam[a]); }
	 }

	 // output/input in -> direction
	 if (pfd[3].revents & POLLOUT) {
	    int cnt= write(pfd[3].fd, obuf, ob_len);
	    if (cnt < 0) {
	       if (errno != EINTR)
		  error("Write failed (%s) on: %s", strerror(errno), chnam[3]);
	       cnt= 0;
	    }
	    memmove(obuf, obuf + cnt, ob_len - cnt);
	    ob_len -= cnt;
	 }
	 if (pfd[0].revents & POLLIN) {
	    ob_len= read(pfd[0].fd, obuf, sz_buf);
	    if (ob_len < 0) {
	       if (errno != EINTR) 
		  error("Read failed (%s) on: %s", strerror(errno), chnam[0]);
	       ob_len= 0;
	    }
	    if (o_dump) dump("->", obuf, ob_len);
	    if (zodrop) ob_len= 0;
	    for (a= 0; otrig > 0 && a<ob_len; a++) if (obuf[a] == '\n') otrig--;
	    if (otrig == 0) { ob_len= a; zexit= o_exit; zodrop= o_drop; zoblock= o_block; }
	    if (o_kbps) owait= 1 | (now + ob_len*8000/o_kbps);	// or with 1 to guarantee non-0
	 }
	    
	 // output/input in <- direction
	 if (pfd[1].revents & POLLOUT) {
	    int cnt= write(pfd[1].fd, ibuf, ib_len);
	    if (cnt < 0) {
	       if (errno != EINTR)
		  error("Write failed (%s) on: %s", strerror(errno), chnam[1]);
	       cnt= 0;
	    }
	    memmove(ibuf, ibuf + cnt, ib_len - cnt);
	    ib_len -= cnt;
	 }
	 if (pfd[2].revents & POLLIN) {
	    ib_len= read(pfd[2].fd, ibuf, sz_buf);
	    if (ib_len < 0) {
	       if (errno != EINTR) 
		  error("Read failed (%s) on: %s", strerror(errno), chnam[2]);
	       ib_len= 0;
	    }
	    if (o_dump) dump("<-", ibuf, ib_len);
	    if (zidrop) ib_len= 0;
	    for (a= 0; itrig > 0 && a<ib_len; a++) if (ibuf[a] == '\n') itrig--;
	    if (itrig == 0) { ib_len= a; zexit= o_exit; zidrop= o_drop; ziblock= o_block; }
	    if (o_kbps) iwait= 1 | (now + ib_len*8000/o_kbps);	// or with 1 to guarantee non-0
	 }
      }
   }	    

   return 0;
}
	  
// END //

/*///---------------------------------------------------------------------\\\*/
/*// )`( )()/)(`/\`//\`          (_)/=\~/_(_)           '/\\'/\')(\()( )'( \\*/
/*/ ()(')()\)(/`/\\ `         (_)  /=\  ~/_  (_)          ' //\'\)(/()(`)() \*/
/*\  )`()( )(/'            (_)    /=\    ~/_    (_)             `\)( )()'(  /*/
/*\\  ()()(`)(/         (_) ____ /=\ ____ ~/_ ____ (_)          \)(')()()  //*/
/*\\\---------------------------------------------------------------------///*/
