/*
 *
 * Copyright 1998-1999, University of Notre Dame.
 * Authors: Jeffrey M. Squyres, Kinis L. Meyer with M. D. McNally 
 *          and Andrew Lumsdaine
 *
 * This file is part of the Notre Dame LAM implementation of MPI.
 *
 * You should have received a copy of the License Agreement for the
 * Notre Dame LAM implementation of MPI along with the software; see
 * the file LICENSE.  If not, contact Office of Research, University
 * of Notre Dame, Notre Dame, IN 46556.
 *
 * Permission to modify the code and to distribute modified code is
 * granted, provided the text of this NOTICE is retained, a notice that
 * the code was modified is included with the above COPYRIGHT NOTICE and
 * with the COPYRIGHT NOTICE in the LICENSE file, and that the LICENSE
 * file is distributed with the modified code.
 *
 * LICENSOR MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED.
 * By way of example, but not limitation, Licensor MAKES NO
 * REPRESENTATIONS OR WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY
 * PARTICULAR PURPOSE OR THAT THE USE OF THE LICENSED SOFTWARE COMPONENTS
 * OR DOCUMENTATION WILL NOT INFRINGE ANY PATENTS, COPYRIGHTS, TRADEMARKS
 * OR OTHER RIGHTS.  
 *
 * Additional copyrights may follow.
 *
 *	Ohio Trollius
 *	Copyright 1997 The Ohio State University
 *	JRV/RBD/GDB
 *
 *	$Id: inetexec.c,v 6.17 1999/08/30 21:38:21 jsquyres Exp $
 * 
 *	Function:	- run program on remote UNIX machine
 *	Accepts:	- hostname
 *			- username (or NULL)
 *			- argv structure
 *	Returns:	- 0 or LAMERROR
 */

#include <lam_config.h>

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#if LAM_NEED_SYS_SELECT_H
#include <sys/select.h>
#endif
#include <sys/wait.h>
#include <sys/param.h>

#include <args.h>
#include <terror.h>
#include <typical.h>
#include "sfh.h"

#ifndef MAXPATHLEN
#define MAXPATHLEN	1024
#endif

#ifndef RSH
#define RSH		"/usr/ucb/rsh"
#endif

/*
 * static variables
 */
static char		shellpath[MAXPATHLEN];	/* return of 'echo $SHELL' */

/*
 * static functions
 */
static int		ioexecvp();
static void             add_rsh(int *argc, char ***argv);

int
inetexec(host, username, argv, prefix)

char			*host;
char			*username;
char			**argv;
char                    *prefix;

{
	char		**cmdv;			/* rsh command argv */
	int		cmdc;			/* rsh command argc */
	int		fl_csh;			/* csh-flavoured flag */
	int		fl_bash;		/* bash-flavoured flag */
	int		i;			/* favorite counter */
	char            printable[BUFSIZ];      /* command to exec */
	char            remote_host[BUFSIZ];    /* username@hostname */
	char            *cmdv0;                 /* Copy of cmdv[0] */
/*
 * Get the user's shell by executing 'echo $SHELL' on remote machine.
 */
	cmdc = 0;
	cmdv = 0;

	add_rsh(&cmdc, &cmdv);
	argvadd(&cmdc, &cmdv, host);
	argvadd(&cmdc, &cmdv, "-n");

	if (username && *username) {
		argvadd(&cmdc, &cmdv, "-l");
		argvadd(&cmdc, &cmdv, username);
	}

	argvadd(&cmdc, &cmdv, "echo $SHELL");

	/* Assemble a string for possible error usage -- argv gets
           freed in ioexecvp */

	printable[0] = '\0';
	for (i = 0; i < cmdc; i++) {
	  strncat(printable, cmdv[i], BUFSIZ);
	  strncat(printable, " ", BUFSIZ);
	}

	if (prefix) {
	  fprintf(stderr, "%s: attempting to execute \"", prefix);
	  for (i = 0; i < cmdc; i++) {
	    if (i > 0)
	      fprintf(stderr, " ");
	    fprintf(stderr, "%s", cmdv[i]);
	  }
	  fprintf(stderr, "\"\n");
	}

	/* Build the username/hostname to pass to the error routine */

	memset(remote_host, 0, 512);
	if (username && *username)
	  snprintf(remote_host, 512, "%s@%s", username, host);
	else
	  strncat(remote_host, host, 512);
	remote_host[511] = '\0';

	cmdv0 = strdup(cmdv[0]);
	if (ioexecvp(cmdv, 0, shellpath, sizeof(shellpath))) {
	  if (errno == EFAULT)
	    show_help("boot", "remote-stderr", remote_host, cmdv0, 
		      "echo $SHELL", printable, NULL);
	  else
	    show_help("boot", "remote-shell-fail", remote_host, cmdv0, 
		      "echo $SHELL", printable, NULL);

	  free(cmdv0);
	  return (LAMERROR);
	}

	/*
	 * Did we get valid output?
	 */
	if (strlen(shellpath) == 0) {
	  show_help("boot", "no-shell", remote_host, cmdv0, "echo $SHELL", 
		    printable, NULL);
	  free(cmdv0);
	  return (LAMERROR);
	}
	free(cmdv0);

	/* 
	 * Perl chomp 
	 */
	if (shellpath[strlen(shellpath) - 1] == '\n')
	  shellpath[strlen(shellpath) - 1] = '\0';
	if (prefix) {
	  fprintf(stderr, "%s: got remote shell %s\n", prefix, shellpath);
	}
	fl_csh = (strstr(shellpath, "csh") != 0) ? TRUE : FALSE;
	fl_bash = (strstr(shellpath, "bash") != 0) ? TRUE : FALSE;
/*
 * Remotely execute the command using "rsh".
 */
	cmdc = 0;
	cmdv = 0;

	add_rsh(&cmdc, &cmdv);
	argvadd(&cmdc, &cmdv, host);
	argvadd(&cmdc, &cmdv, "-n");

	if (username && *username) {
		argvadd(&cmdc, &cmdv, "-l");
		argvadd(&cmdc, &cmdv, username);
	}

#if NEED_RSH_MINUSMINUS
/*
 * Stop rsh from interpreting options for the remote command as
 * rsh options.
 */
        argvadd(&cmdc, &cmdv, "--");
#endif

/*
 * If the user's shell is not based on "csh" or "bash", force the
 * interpretation of the user's .profile script in order to initialize
 * the paths. This works for "sh" and "ksh".
 */
	if (!(fl_csh || fl_bash)) argvadd(&cmdc, &cmdv, "(. ./.profile;");
	for (i = 0; argv[i]; ++i) {
		argvadd(&cmdc, &cmdv, argv[i]);
	}

	if (!(fl_csh || fl_bash)) argvadd(&cmdc, &cmdv, ")");

	if (prefix) {
	  fprintf(stderr, "%s: attempting to execute \"", prefix);
	  for (i = 0; i < cmdc; i++) {
	    if (i > 0)
	      fprintf(stderr, " ");
	    fprintf(stderr, "%s", cmdv[i]);
	  }
	  fprintf(stderr, "\"\n");
	}

	cmdv0 = strdup(cmdv[0]);
	i = ioexecvp(cmdv, 1, (char *) 0, 0);

	/* Do we need to print an error message? */

	if (i) {
	  int i;
	  char cmd[512];

	  /* Build the sample command to pass to the error routine */

	  cmd[0] = '\0';
	  for (i = 0; i < cmdc; i++) {
	    strncat(cmd, cmdv[i], 512);
	    strncat(cmd, " ", 512);
	  }

	  if (errno == EFAULT)
	    show_help("boot", "remote-stderr", remote_host, cmdv0,
		      argv[0], cmd, NULL);
	  else
	    show_help("boot", "remote-boot-fail", remote_host, cmdv0,
		      argv[0], cmd, NULL);

	  free(cmdv0);
	  return(LAMERROR);
	}

	free(cmdv0);
	return 0;
}

/*
 *	ioexecvp
 *
 *	Function:	- execute command (similar to cnfexec)
 *			- can direct command stdout to buffer and/or stdout
 *			- stderr is checked and passed through
 *	Accepts		- command argv
 *			- print stdout flag
 *			- ptr to buffer (for stdout data)
 *			- size of buffer
 *	Returns		- 0 or LAMERROR
 */
static int
ioexecvp(cmdv, showout, outbuff, outbuffsize)

char			**cmdv;
int			showout;
char			*outbuff;
int			outbuffsize;

{
	int		kidstdout[2];		/* child stdout pipe */
	int		kidstderr[2];		/* child stderr pipe */
	int		ret;			/* read() return value */
	int		err;			/* error indicator */
	int		status;			/* exit status */
	int		pid;			/* child process id */
	char		*ptr = 0;		/* buffer pointer */
	fd_set          fdset;                  /* fd's for select */
	int             nfds = 1;               /* num fd's in fdset */
	char            temp[256];              /* string holding space */
	int             want_out = 0;           /* want stdout in select */
/*
 * Create child stdout/stderr pipes and fork the child process (command).
 */
	if (pipe(kidstdout) || pipe(kidstderr)) return(LAMERROR);

	if ((pid = fork()) < 0) {
		return(LAMERROR);
	}

	else if (pid == 0) {				/* child */

		if ((dup2(kidstderr[1], 2) < 0) ||
				(dup2(kidstdout[1], 1) < 0)) {
			perror(cmdv[0]);
			exit(errno);
		}

		if (close(kidstdout[0]) || close(kidstderr[0]) ||
				close(kidstdout[1]) || close(kidstderr[1])) {
			perror(cmdv[0]);
			exit(errno);
		}

		execvp(cmdv[0], cmdv);
		exit(errno);
	}

	if (close(kidstdout[1]) || close(kidstderr[1])) return(LAMERROR);

	argvfree(cmdv);
/* 
 * We must be able to monitor both stdout and stderr; it is possible
 * that we may be trying to capture the stdout but also need to
 * monitor output on stderr (e.g., recon, lamboot).  So make a FD_SET
 * with potentially both of the file descriptors and do a select on
 * it.
 */
	FD_ZERO(&fdset);
	FD_SET(kidstderr[0], &fdset);
	nfds = kidstderr[0] + 1;
	if (showout || (outbuff != 0)) {
	  ptr = outbuff;
	  FD_SET(kidstdout[0], &fdset);
	  nfds = (nfds > kidstdout[0] + 1) ? nfds : kidstdout[0] + 1;
	  want_out = 1;
	}

	err = 0;
	while (nfds > 0 && 
	       (ret = select(nfds, &fdset, NULL, NULL, NULL)) > 0) {
	  /*
	   * See if there was something on stderr 
	   */
	  if (FD_ISSET(kidstderr[0], &fdset) != 0) {
	    ret = read(kidstderr[0], temp, 256);
	    if (ret > 0) {
	      err = 1;
	      write(2, temp, ret);
	      fflush(stderr);

	      errno = EFAULT;
	      err = LAMERROR;
	    }
	    break;
	  }

	  /*
	   * See if there is something on stdout (and if we care)
	   */
	  if ((showout || (outbuff != 0)) && 
	      FD_ISSET(kidstdout[0], &fdset) != 0) {
	    ret = read(kidstdout[0], temp, 256);
	    if (ret > 0) {
	      if (outbuffsize > 0) {
		memcpy(ptr, temp, (ret > outbuffsize) ? outbuffsize : ret);
		/* Doesn't matter if we overshoot here */
		outbuffsize -= ret;
		ptr += ret;
	      }
	      if (showout) {
		write(0, temp, ret);
		fflush(stdout);
	      }
	    } else
	      want_out = 0;
	  }

	  /* 
	   * Reset stderr, 'cause we're always interested in that 
	   */
	  FD_SET(kidstderr[0], &fdset);
	  nfds = kidstderr[0] + 1;

	  /*
	   * See if we want to reset stdout 
	   */
	  if (want_out) {
	    FD_SET(kidstdout[0], &fdset);
	    nfds = (nfds > kidstdout[0] + 1) ? nfds : kidstdout[0] + 1;
	  }
	}

	if (err != 0)
	  return (LAMERROR);
/*
 * Close the pipes of the parent process.
 */
	if (close(kidstdout[0]) || close(kidstderr[0])) {
		err = LAMERROR;
	}
/*
 * Wait for the command to exit.
 */
	do {
		if (waitpid(pid, &status, 0) < 0) {
			return(LAMERROR);
		}
	} while (! WIFEXITED(status));
	
	if (WEXITSTATUS(status)) {
		errno = WEXITSTATUS(status);

		if (errno == 1) {
			errno = EUNKNOWN;
		}

		return(LAMERROR);
	}

	return(err);
}


/*
 *	add_rsh
 *
 *	Function:	- add the RSH command to the argc/argv array
 *			- use value in LAMRSH environment variable (if exists)
 *			- or use default RSH string
 *			- be sure to parse into individual words to add nicely
 *	Accepts		- ptr to length of array
 *			- prt to array
 */
static void
add_rsh(int *cmdc, char ***cmdv)
{
  char		*prsh_orig;		/* override default rsh cmd */
  char		*prsh;		        /* override default rsh cmd */
  
  prsh_orig = getenv("LAMRSH");
  if (prsh_orig == 0) prsh_orig = RSH;

  /*
   * Split into individual terms 
   */
  prsh = strdup(prsh_orig);
  *cmdv = sfh_argv_break(prsh, ' ');
  *cmdc = sfh_argv_count(*cmdv);
  free(prsh);
}
