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

//
//      Remote connection code
//

//	In this file, there are two types of error() call: error() and
//	error_badlink(); the second is used for errors that indicate
//	that the link is broken, so that no attempt is made to close
//	the connection nicely (with a 'quit'->OK response)

//
//  Connect/disconnect:
//	void pipe_conn(char *cmd);
//	void pipe_disconn();
//	void pipe_remote();	// Used on remote end to setup with FD 0/1
//
//  Write various things:
//	void pipe_wrlin(char *fmt, ...);
//	void pipe_wrOK();
//	void pipe_wrmem(char *buf, int len);
//	void pipe_wrfile(char *fnam, int len);
//
//  Read various things:
//	char *pipe_rdlin();
//	void pipe_rdmem(char *buf, int len);
//	void pipe_rdfile(char *fnam, int len);
//	void pipe_rdOK(char *cmdnam);
//

#ifdef HEADER


#else

#include "all.h"

static int in, out;		// In/out file descriptors
static char *inbuf= 0;		// Input buffer
static int ib_len;		// Length of valid data in inbuf[]
static int ib_off;		// Offset of next byte to read in inbuf[]
static int ib_max= 0;		// Size of inbuf[] buffer
static char *outbuf= 0;		// Output buffer
static int ob_max= 0;		// Output buffer size
pid_t rem_pid;			// Remote command PID, or 0 if we are not connected


// Prototypes
static void write_out(char *buf, int len);
static void read_in();
static void sigchld_handler(int sig);
static void alarm_handler(int sig);

// Special error handler to use when the link is broken
static void
error_badlink(char *fmt, ...) {
   va_list ap;
   va_start(ap, fmt);
   rem_pid= 0;		// Don't attempt to disconnect
   verror(fmt, ap);
}

// Initialise inbuf + outbuf
static void 
setup_iobuf() {
   if (!inbuf) inbuf= Alloc(ib_max= 16384);
   ib_len= ib_off= 0;
   if (!outbuf) outbuf= Alloc(ob_max= 4096);
}


//
//	Send a formatted command-string to the remote connection
//	(newline is added)
//

void
pipe_wrlin(char *fmt, ...) {
   va_list ap;
   int len;

 retry:
   va_start(ap, fmt);
   len= vsnprintf(outbuf, ob_max, fmt, ap);
   va_end(ap);
   if (len < 0 || len >= ob_max-1) {
      if (ob_max >= 65536) 
	 error("pipe_wrlin() attempt to format a line longer than 64K (%d)\n  %s", len, fmt);
      free(outbuf); outbuf= Alloc(ob_max *= 2);
      goto retry;
   }

   outbuf[len++]= '\n';
   write_out(outbuf, len);
   if (o_debug) warn(o_local ? ">> %.*s" : "**>> %.*s", len-1, outbuf);
}


//
//	Write an OK response
//

void 
pipe_wrOK() {
   pipe_wrlin("OK");
}


//
//	Send block of data from memory
//

void 
pipe_wrmem(char *buf, int len) {
   write_out(buf, len);
}


//
//	Send raw data to the remote connection from the given file;
//	'len' must be provided in case file size has changed.  It
//	would be a bigger error to send the wrong length of data than
//	to send a length other than that expected by the other end.
//

void 
pipe_wrfile(char *fnam, int len) {
   char buf[16384];
   FILE *fp;

   fp= fopen(fnam, "rb");
   if (!fp) error("Can't open file for reading: %s", fnam);

   while (len) {
      // Fill buffer
      int cnt= sizeof(buf);
      if (cnt > len) cnt= len;
      if (1 != fread(buf, cnt, 1, fp))
	 error_badlink("Read error on input file, or file-size (%d) has changed: %s", len, fnam);
      len -= cnt;

      write_out(buf, cnt);
   }

   fclose(fp);
}


//
//	Read a single line from the remote connection; returns a
//	pointer to a NUL-terminated string in the input buffer (does
//	not need to be free'd), without the trailing newline.  This
//	string is valid until the next rd*() call to read data from
//	the connection.  Any "# <message>" lines that come through are
//	dumped to stderr and ignored.
//

char *
pipe_rdlin() {
   char *p, *str, *end;

   while (1) {
      // See if we have a whole line up to a '\n' available
      str= inbuf + ib_off;
      end= inbuf + ib_len;
      for (p= str; p<end; p++) 
	 if (*p == '\n') {
	    if (p > str && p[-1] == '\r') p[-1]= 0;
	    *p++= 0;
	    ib_off= p-inbuf;

	    if (str[0] == '#') {
	       warn(o_local ? "<< %s" : "**<< %s", str);
	       str= p; 
	       end= inbuf + ib_len;
	       p--; continue;
	    }

	    if (o_debug) warn(o_local ? "<< %s" : "**<< %s", str);
	    return str;
	 }

      // Need to read some more data
      if (ib_off == 0 && ib_len == ib_max)
	 error_badlink(o_local ? 
		       "Bad data sent by remote symirr; response line longer than %d bytes" :
		       "Bad data sent by controlling symirr; command line longer than %d bytes", 
		       ib_max);
      read_in();
   }

   return 0;
}


//
//	Read a block of binary data into the given buffer.
//

void 
pipe_rdmem(char *buf, int len) {
   while (len) {
      // Copy whatever we can from the buffer
      int cnt= ib_len - ib_off;
      if (cnt > 0) {
	 if (cnt > len) cnt= len;

	 memcpy(buf, inbuf+ib_off, cnt);
	 buf += cnt;
	 len -= cnt;
	 ib_off += cnt;
	 continue;
      }
      
      // Need to read some more data
      read_in();
   }
}


//
//	Read a (large) block of binary data from the remote connection
//	and save it to the given filename.
//

void 
pipe_rdfile(char *fnam, int len) {
   FILE *fp;

   fp= fopen(fnam, "wb");
   if (!fp) error("Can't create file for writing: %s", fnam);

   while (len) {
      // Copy whatever we can from the buffer
      int cnt= ib_len - ib_off;
      if (cnt > 0) {
	 if (cnt > len) cnt= len;
	 if (1 != fwrite(inbuf + ib_off, cnt, 1, fp))
	    error("Write error on output file: %s", fnam);
	 ib_off += cnt;
	 len -= cnt;
	 continue;
      }
      
      // Need to read some more data
      read_in();
   }

   if (0 != fclose(fp))
      error("Write error on output file: %s", fnam);
}


//
//	Read a command reply, and drop out with an error if it is not
//	"OK"
//

void
pipe_rdOK(char *cmdnam) {
   char *p= pipe_rdlin();
   if (!str_eq(p, "OK")) 
      error("Error return from '%s' command:\n  %s", cmdnam, p);
}
   

//
//	Create a remote connection by executing the given shell
//	command, and hook up input/output pipes so we can talk to it.
//	If 'local' is set, then instead of starting a new executable,
//	a call to do_remote() is done instead with 'base' set to
//	av[0].
//

void 
pipe_conn(char **av) {
   int pout[2], pin[2];
   pid_t pid;

   if (!inbuf || !outbuf) setup_iobuf();

   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
      // Set SIGINT to ignore or else 'ssh' catches ^C and dies before
      // we can do a clean termination of the sync
      if (SIG_ERR == signal(SIGINT, SIG_IGN))
	 error("Can't set SIGINT signal handler to be ignored for remote connect command");

      // 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]);

      //WAS: execl("/bin/sh", "/bin/sh", "-c", cmd, NULL);
   }

   rem_pid= pid;
   if (SIG_ERR == signal(SIGCHLD, sigchld_handler))
      error("Can't set SIGCHLD signal handler");
   if (SIG_ERR == signal(SIGALRM, alarm_handler))
      error("Can't set SIGALRM signal handler");
   
   // 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);
}


//
//	Disconnect
//
   
void 
pipe_disconn() {
   if (!rem_pid) return;
   rem_pid= 0;	// Don't report final exit status from SIGCHLD handler
   pipe_wrlin("quit");
   pipe_rdlin();		// Read but ignore OK/ERR response
   close(out);
   close(in);
}


//
//	Setup for remote end, using FD 0/1 for in/out
//

void 
pipe_remote() {
   in= 0;
   out= 1;
   rem_pid= 0;

   if (!inbuf || !outbuf) setup_iobuf();

   if (SIG_ERR == signal(SIGALRM, alarm_handler))
      error("Can't set SIGALRM signal handler");
}


//
//	Local functions to handle reading/writing data to/from remote
//	command.
//

// Write a chunk of memory out
static void 
write_out(char *buf, int len) {
   int rv;

   while (len > 0) {
      alarm(timeout);
      rv= write(out, buf, len);
      alarm(0);

      if (rv < 0) {
	 if (errno == EAGAIN || errno == EINTR) continue;
	 error_badlink(o_local ? 
		       "Remote-connection write error: %s" :
		       "Error sending data to controlling symirr: %s",
		       strerror(errno));
      }
      buf += rv;
      len -= rv;
   }
}

// Read as much data as is available right now into inbuf[]
static void 
read_in() {
   int rv;

   if (ib_off > 0) { 
      memmove(inbuf, inbuf+ib_off, ib_len-ib_off);
      ib_len -= ib_off;
      ib_off= 0;
   }

   alarm(timeout);
   rv= read(in, inbuf+ib_len, ib_max-ib_len);
   alarm(0);

   if (rv < 0) {
      if (errno == EAGAIN || errno == EINTR) return;
      error_badlink(o_local ? 
		    "Remote-connection read error: %s" :
		    "Error reading data from controlling symirr: %s",
		    strerror(errno));
   }
   ib_len += rv;
}

static void
alarm_handler(int sig) {
   error_badlink(o_local ? 
		 "Remote symirr unresponsive: time-out after %ds" :
		 "Controlling symirr unresponsive: time-out after %ds",
		 timeout);
}

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) {
      if (!WIFEXITED(status))
	 error_badlink("Remote command exited abnormally");
      error_badlink("Remote command exited with code: %d", WEXITSTATUS(status));
      return;        // Return without setting up another handler
   }

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

#endif

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