/* ``The contents of this file are subject to the Erlang Public License,
 * Version 1.1, (the "License"); you may not use this file except in
 * compliance with the License. You should have received a copy of the
 * Erlang Public License along with this software. If not, it can be
 * retrieved via the world wide web at http://www.erlang.org/.
 * 
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Initial Developer of the Original Code is Ericsson Utvecklings AB.
 * Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
 * AB. All Rights Reserved.''
 * 
 *     $Id$
 */

/* An exception from using eidef.h, use config.h directly */
#include "config.h"

#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>

#ifdef __WIN32__
#include <winsock2.h>
#include <windows.h>
#include <winbase.h>

#elif VXWORKS
#include <stdio.h>
#include <string.h>
#include <vxWorks.h>
#include <hostLib.h>
#include <selectLib.h>
#include <ifLib.h>
#include <sockLib.h>
#include <taskLib.h>
#include <inetLib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h> 
#include <symLib.h>
#include <sysSymTbl.h>
#include <sysLib.h>
#include <tickLib.h>

#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif

#include <a_out.h>

/* #include "netdb.h" */
#else /* other unix */
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#endif

#include "ei.h"
#include "ei_resolve.h"
#include "erl_start.h"

/* FIXME is this a case a vfork can be used? */
#if !HAVE_WORKING_VFORK
# define vfork fork
#endif

#ifndef MAXPATHLEN
#define MAXPATHLEN 1024
#endif

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

/* FIXME check errors from malloc */

static struct in_addr *get_addr(const char *hostname, struct in_addr *oaddr);

static int wait_for_erlang(int sockd, int magic, struct timeval *timeout);
#if defined(VXWORKS) || defined(__WIN32__)
static int unique_id(void);
static unsigned long spawn_erlang_epmd(ei_cnode *ec,
				       char *alive,
				       Erl_IpAddr adr,
				       int flags, 
				       char *erl_or_epmd, 
				       char *args[], 
				       int port,
				       int is_erlang);
#else
static int exec_erlang(ei_cnode *ec, char *alive, Erl_IpAddr adr, int flags,
		       char *erl, char *args[],int port);
#endif
/* Start an Erlang node. return value 0 indicates that node was
 * started successfully, negative values indicate error. 
 * 
 * node -  the name of the remote node to start (alivename@hostname).
 * flags - turn on or off certain options. See erl_start.h for a list.
 * erl -  is the name of the erl script to call. If NULL, the default
 * name "erl" will be used.
 * args - a NULL-terminated list of strings containing
 * additional arguments to be sent to the remote Erlang node. These
 * strings are simply appended to the end of the command line, so any
 * quoting of special characters, etc must be done by the caller.
 * There may be some conflicts between some of these arguments and the
 * default arguments hard-coded into this function, so be careful. 
 */
int erl_start_sys(ei_cnode *ec, char *alive, Erl_IpAddr adr, int flags,
		  char *erl, char *args[])
{
  struct timeval timeout;
  struct sockaddr_in addr;
  int namelen;
  int port;
  int sockd = 0;
  int one = 1;
#if defined(VXWORKS) || defined(__WIN32__)
  unsigned long pid = 0;
#else
  int pid = 0;
#endif
  int r = 0;

  if (((sockd = socket(AF_INET, SOCK_STREAM, 0)) < 0) ||
      (setsockopt(sockd, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one)) < 0)) {
    r = ERL_SYS_ERROR;
    goto done;
  }
  
  memset(&addr,0,sizeof(addr));
  addr.sin_family = AF_INET;              
  addr.sin_addr.s_addr = htonl(INADDR_ANY); 
  addr.sin_port = 0;

  if (bind(sockd,(struct sockaddr *)&addr,sizeof(addr))<0) {
      return ERL_SYS_ERROR;
  }
  namelen = sizeof(addr);
  if (getsockname(sockd,(struct sockaddr *)&addr,&namelen)<0) {
      return ERL_SYS_ERROR;
  }
  port = ntohs(addr.sin_port);

  listen(sockd,5);

#if defined(VXWORKS) || defined(__WIN32__)
  if((pid = spawn_erlang_epmd(ec,alive,adr,flags,erl,args,port,1)) 
      == 0)
     return ERL_SYS_ERROR;
  timeout.tv_usec = 0;
  timeout.tv_sec = 10; /* ignoring ERL_START_TIME */
  if((r = wait_for_erlang(sockd,unique_id(),&timeout)) 
     == ERL_TIMEOUT) {
#if defined(VXWORKS)
      taskDelete((int) pid);
      if(taskIdVerify((int) pid) != ERROR)
	  taskDeleteForce((int) pid);
#else /* Windows */
      /* Well, this is not a nice way to do it, and it does not 
	 always kill the emulator, but the alternatives are few.*/
      TerminateProcess((HANDLE) pid,1);
#endif /* defined(VXWORKS) */
  }
#else /* Unix */
  switch ((pid = fork())) {
  case -1:
    r = ERL_SYS_ERROR;
    break;

  case 0:
    /* child - start the erlang node */
    exec_erlang(ec, alive, adr, flags, erl, args, port);

    /* error if reached - parent reports back to caller after timeout
       so we just exit here */
    exit(1);
    break;

  default:

    /* parent - waits for response from Erlang node */
    /* child pid used here as magic number */
    timeout.tv_usec = 0;
    timeout.tv_sec = 10; /* ignoring ERL_START_TIME */
    if ((r = wait_for_erlang(sockd,pid,&timeout)) == ERL_TIMEOUT) {
      /* kill child if no response */
      kill(pid,SIGINT);
      sleep(1);
      if (waitpid(pid,NULL,WNOHANG) != pid) {
	/* no luck - try harder */
	kill(pid,SIGKILL);
	sleep(1);
	waitpid(pid,NULL,WNOHANG);
      }
    }

  }
#endif /* defined(VXWORKS) || defined(__WIN32__) */

done:
#if defined(__WIN32__)
  if (sockd) closesocket(sockd);
#else
  if (sockd) close(sockd);
#endif
  return r;
} /* erl_start_sys() */

#if defined(VXWORKS) || defined(__WIN32__)
#if defined(VXWORKS)
#define DEF_ERL_COMMAND ""
#define DEF_EPMD_COMMAND ""
#define ERLANG_SYM "start_erl"
#define EPMD_SYM "start_epmd"
#define ERL_REPLY_FMT   "-s erl_reply reply %s %d %d"
#else
#define DEF_ERL_COMMAND "erl"
#define DEF_EPMD_COMMAND "epmd"
#define ERL_REPLY_FMT   "-s erl_reply reply \"%s\" \"%d\" \"%d\""
#endif
#define ERL_NAME_FMT    "-noinput -name %s"
#define ERL_SNAME_FMT   "-noinput -sname %s"

#define IP_ADDR_CHARS 15
#define FORMATTED_INT_LEN 10

static int unique_id(void){
#if defined(VXWORKS)
    return taskIdSelf();
#else 
    return (int) GetCurrentThreadId();
#endif
}

static int enquote_args(char **oargs, char ***qargs){
    char **args;
    int len;
    int i;
    int qwhole;
    int extra;
    char *ptr;
    char *ptr2;

    if(oargs == NULL){
	*qargs = malloc(sizeof(char *));
	**qargs = NULL;
	return 0;
    };

    for(len=0;oargs[len] != NULL; ++len)
	;
    args = malloc(sizeof(char *) * (len + 1));
    
    for(i = 0; i < len; ++i){
	qwhole = strchr(oargs[i],' ') != NULL;
	extra = qwhole * 2;
	for(ptr = oargs[i]; *ptr != '\0'; ++ptr)
	    extra += (*ptr == '"');
	args[i] = malloc(strlen(oargs[i]) +
			     extra +
			     1);
	ptr2 = args[i];
	if(qwhole)
	    *(ptr2++) = '"';
	for(ptr = oargs[i]; *ptr != '\0'; ++ptr){
	    if(*ptr == '"')
		*(ptr2++) = '\\';
	    *(ptr2++) = *ptr;
	}
	if(qwhole)
	    *(ptr2++) = '"';
	*ptr2 = '\0';
    }
    args[len] = NULL;
    *qargs = args;
    return len;
}

static void free_args(char **args){
    char **ptr = args;
    while(*ptr != NULL)
	free(*(ptr++));
    free(args);
}

#if defined(VXWORKS)
static  FUNCPTR lookup_function(char *symname){
    char *value;
    SYM_TYPE type;
    if(symFindByName(sysSymTbl,
		     symname,
		     &value,
		     &type) == ERROR /*|| type != N_TEXT*/)
	return NULL;
    return (FUNCPTR) value;
}
#endif /* defined(VXWORKS) */

/* In NT and VxWorks, we cannot fork(), Erlang and Epmd gets 
   spawned by this function instead. */

static unsigned long spawn_erlang_epmd(ei_cnode *ec,
				       char *alive,
				       Erl_IpAddr adr,
				       int flags, 
				       char *erl_or_epmd, 
				       char *args[], 
				       int port,
				       int is_erlang)
{
#if defined(VXWORKS)
    FUNCPTR erlfunc;
#else /* Windows */
    STARTUPINFO sinfo; 
    SECURITY_ATTRIBUTES sa;
    PROCESS_INFORMATION pinfo;
#endif
    char *cmdbuf;
    int cmdlen;
    char *ptr;
    int i;
    int num_args;
    char *name_format;
    struct in_addr myaddr;
    struct in_addr *hisaddr = (struct in_addr *)adr;
    char iaddrbuf[IP_ADDR_CHARS + 1];
    int ret;

    if(is_erlang){
	get_addr(ei_thishostname(ec), &myaddr);
#if defined(VXWORKS)
     	inet_ntoa_b(myaddr, iaddrbuf);
#else /* Windows */
	if((ptr = inet_ntoa(myaddr)) == NULL)
	    return 0;
	else
	    strcpy(iaddrbuf,ptr);
#endif
    }
    if ((flags & ERL_START_REMOTE) || 
	(is_erlang && (hisaddr->s_addr != myaddr.s_addr))) {
	return 0;
    } else {
	num_args = enquote_args(args, &args);
	for(cmdlen = i = 0; args[i] != NULL; ++i)
	    cmdlen += strlen(args[i]) + 1;
#if !defined(VXWORKS)
	/* On VxWorks, we dont actually run a command,
	   we call start_erl() */
	if(!erl_or_epmd) 
#endif
	    erl_or_epmd = (is_erlang) ? DEF_ERL_COMMAND :
	    DEF_EPMD_COMMAND;
	if(is_erlang){
	    name_format = (flags & ERL_START_LONG) ? ERL_NAME_FMT :
		ERL_SNAME_FMT;
	    cmdlen += 
		strlen(erl_or_epmd) + (*erl_or_epmd != '\0') +
		strlen(name_format) + 1 + strlen(alive) +
		strlen(ERL_REPLY_FMT) + 1 + strlen(iaddrbuf) +  
	            2 * FORMATTED_INT_LEN +
		1;
	    ptr = cmdbuf = malloc(cmdlen);
	    if(*erl_or_epmd != '\0')
		ptr += sprintf(ptr,"%s ",erl_or_epmd);
	    ptr += sprintf(ptr, name_format,
			   alive);
	    ptr += sprintf(ptr, " " ERL_REPLY_FMT,
		       iaddrbuf, port, unique_id());
	} else { /* epmd */
	    cmdlen += strlen(erl_or_epmd) + (*erl_or_epmd != '\0') + 1;
	    ptr = cmdbuf = malloc(cmdlen);
	    if(*erl_or_epmd != '\0')
		ptr += sprintf(ptr,"%s ",erl_or_epmd);
	    else
		*(ptr++) = '\0';
	}
	for(i= 0; args[i] != NULL; ++i){
	    *(ptr++) = ' ';
	    strcpy(ptr,args[i]);
	    ptr += strlen(args[i]);
	}
	free_args(args);
	if (flags & ERL_START_VERBOSE) {
	    fprintf(stderr,"erl_call: commands are %s\n",cmdbuf);
	}
	/* OK, one single command line... */
#if defined(VXWORKS)
	erlfunc = lookup_function((is_erlang) ? ERLANG_SYM :
				  EPMD_SYM);
	if(erlfunc == NULL){
	    if (flags & ERL_START_VERBOSE) {
		fprintf(stderr,"erl_call: failed to find symbol %s\n",
			(is_erlang) ? ERLANG_SYM : EPMD_SYM);
	    }
	    ret = 0;
	} else {
	/* Just call it, it spawns itself... */
	    ret = (unsigned long) 
		(*erlfunc)((int) cmdbuf,0,0,0,0,0,0,0,0,0);
	    if(ret == (unsigned long) ERROR)
		ret = 0;
	}
#else /* Windows */
	/* Hmmm, hidden or unhidden window??? */
	memset(&sinfo,0,sizeof(sinfo));
	sinfo.cb = sizeof(STARTUPINFO); 
	sinfo.dwFlags = STARTF_USESHOWWINDOW /*| 
	    STARTF_USESTDHANDLES*/;
	sinfo.wShowWindow = SW_HIDE; /* Hidden! */
	sinfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
	sinfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	sinfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
	sa.nLength = sizeof(sa);
	sa.lpSecurityDescriptor = NULL;
	sa.bInheritHandle = /*TRUE*/ FALSE;
	if(!CreateProcess(
			  NULL,
			  cmdbuf,
			  &sa,
			  NULL,
			  /*TRUE*/ FALSE,
			  0 | CREATE_NEW_CONSOLE,
			  NULL,
			  NULL,
			  &sinfo,
			  &pinfo))
	    ret = 0;
	else
	    ret = (unsigned long) pinfo.hProcess;
#endif
	free(cmdbuf);
	return ret;
    }
    /* NOTREACHED */
}
#else /* Unix */

/* call this from the child process to start an erlang system. This
 * function just builds the erlang command line and then calls it.
 *
 * node - the nodename for the new node
 * flags - various options that can be set (see erl_start.h)
 * erl - name of the erlang executable, or NULL for default ("erl")
 * args - additional arguments to pass to erlang executable
 * port - the port number where we wait for acknowledgment from the enode
 *
 * we have a potential problem if args conflicts with any of the 
 * arguments we use here.
 */
static int exec_erlang(ei_cnode *ec,
		       char *alive, 
		       Erl_IpAddr adr,
		       int flags, 
		       char *erl, 
		       char *args[], 
		       int port)
{
#if !defined(__WIN32__) && !defined(VXWORKS) 
  int fd,len,l,i;
  char **s;
  char *argv[4];
  char argbuf[BUFSIZ];
  struct in_addr myaddr;
  struct in_addr *hisaddr = (struct in_addr *)adr;
  
  get_addr(ei_thishostname(ec), &myaddr);

  /* on this host? */
  /* compare ip addresses, unless forced by flag setting to use rsh */
  if ((flags & ERL_START_REMOTE) || (hisaddr->s_addr != myaddr.s_addr)) {
    argv[0] = RSH;
    len = strlen(inet_ntoa(*hisaddr));
    argv[1] = malloc(len+1);
    strcpy(argv[1],inet_ntoa(*hisaddr));
  }
  else {
  /* Yes - use sh to start local Erlang */
    argv[0] = "sh";
    argv[1] = "-c";
  }
  argv[2] = argbuf;
  argv[3] = NULL;

  len = 0;
  *argbuf=(char)0;

  sprintf(argbuf,"exec %s ", (erl? erl: "erl"));
  len = strlen(argbuf);

  /* *must* be noinput or node (seems to) hang... */
  /* long or short names? */
  sprintf(&argbuf[len], "-noinput %s %s ", 
	  ((flags & ERL_START_LONG) ? "-name" : "-sname"), 
	  alive);
  len = strlen(argbuf);

  /* now make the new node report back when it's ready */
  /* add: myip, myport and replymsg */
  sprintf(&argbuf[len],
	  "-s erl_reply reply %s %d %d ",
	  inet_ntoa(myaddr),port,(int)getpid());
#ifdef DEBUG
  fprintf(stderr,"erl_call: debug %s\n",&argbuf[len]);
#endif
  len = strlen(argbuf);

  /* additional arguments to be passed to the other system */
  /* make sure that they will fit first */
  for (l=0, s = args; s && *s; s++) l+= strlen(*s) + 1;

  if (len + l + 1 > BUFSIZ) return ERL_BADARG;
  else {
    for (s = args; s && *s; s++) {
      strcat(argbuf," ");
      strcat(argbuf,*s);
    }
    len += l + 1;
  }

  if (flags & ERL_START_VERBOSE) {
    fprintf(stderr,"erl_call: %s %s %s\n",argv[0],argv[1],argv[2]);
  }

  /* close all descriptors in child */
  for (i=0; i<64; i++) close(i);

  /* debug output to file? */
  if (flags & ERL_START_DEBUG) {
    char debugfile[MAXPATHLEN+1];
    char *home=getenv("HOME");
    sprintf(debugfile,"%s/%s.%s",home,ERL_START_LOGFILE,alive);
    if ((fd=open(debugfile, O_WRONLY | O_CREAT | O_APPEND, 0644)) >= 0) {
      time_t t = time(NULL);
      dup2(fd,1);
      dup2(fd,2);
      fprintf(stderr,"\n\n===== Log started ======\n%s \n",ctime(&t));
      fprintf(stderr,"erl_call: %s %s %s\n",argv[0],argv[1],argv[2]);
    }
  }     

  /* start the system */
  execvp(argv[0], argv);

  if (flags & ERL_START_DEBUG) {
    fprintf(stderr,"erl_call: exec failed: (%d) %s %s %s\n",
	    errno,argv[0],argv[1],argv[2]);
  }

#endif
  /* (hopefully) NOT REACHED */
  return ERL_SYS_ERROR;
} /* exec_erlang() */

#endif /* defined(VXWORKS) || defined(WINDOWS) */

#if defined(__WIN32__)
static void gettimeofday(struct timeval *now,void *dummy){
    SYSTEMTIME systime;
	FILETIME ft;
    DWORD x;
    GetSystemTime(&systime);
    SystemTimeToFileTime(&systime,&ft);
    x = ft.dwLowDateTime / 10;
    now->tv_sec = x / 1000000;
    now->tv_usec = x % 1000000;
}

#elif defined(VXWORKS)
static void gettimeofday(struct timeval *now, void *dummy){
    int rate = sysClkRateGet(); /* Ticks per second */
    unsigned long ctick = tickGet();
    now->tv_sec = ctick / rate; /* secs since reboot */
    now->tv_usec = ((ctick - (now->tv_sec * rate))*1000000)/rate;
}
#endif
       

/* wait for the remote system to reply */
/*
 * sockd - an open socket where we expect a connection from the e-node
 * magic - sign on message the e-node must provide for verification
 * timeout - how long to wait before returning failure
 *
 * OBS: the socket is blocking, and there is a potential deadlock if we
 * get an accept but the peer sends no data (and does not close).
 * in normal cases the timeout will work ok however, i.e. either we
 * never get any connection, or we get connection then close().
 */
static int wait_for_erlang(int sockd, int magic, struct timeval *timeout)
{
  struct timeval to;
  struct timeval stop_time;
  struct timeval now;
  fd_set rdset;
  int fd;
  int n,i;
  char buf[16];
  struct sockaddr_in peer;
  int len = sizeof(peer);

  /* determine when we should exit this function */
  gettimeofday(&now,NULL);
  stop_time.tv_sec = now.tv_sec + timeout->tv_sec;
  stop_time.tv_usec = now.tv_usec + timeout->tv_usec;
  while (stop_time.tv_usec > 1000000) {
    stop_time.tv_sec++;
    stop_time.tv_usec -= 1000000;
  }

#ifdef DEBUG
  fprintf(stderr,"erl_call: debug time is %ld.%06ld, "
	  "will timeout at %ld.%06ld\n",
	  now.tv_sec,now.tv_usec,stop_time.tv_sec,stop_time.tv_usec);
#endif
  
  while (1) {
    FD_ZERO(&rdset);
    FD_SET(sockd,&rdset);

    /* adjust the timeout to (stoptime - now) */
    gettimeofday(&now,NULL);
    to.tv_sec = stop_time.tv_sec - now.tv_sec;
    to.tv_usec = stop_time.tv_usec - now.tv_usec;
    while ((to.tv_usec <= 0) && (to.tv_sec >= 0)) {
      to.tv_usec += 1000000;
      to.tv_sec--;
    }
    if (to.tv_sec < 0) return ERL_TIMEOUT;
    
#ifdef DEBUG
    fprintf(stderr,"erl_call: debug remaining to timeout: %ld.%06ld\n",
	    to.tv_sec,to.tv_usec);
#endif
    switch ((i = select(sockd+1,&rdset,NULL,NULL,&to))) {
    case -1:
      return ERL_SYS_ERROR;
      break;

    case 0: /* timeout */
#ifdef DEBUG
      gettimeofday(&now,NULL);
      fprintf(stderr,"erl_call: debug timed out at %ld.%06ld\n",
	      now.tv_sec,now.tv_usec);
#endif
      return ERL_TIMEOUT;
      break;

    default: /* ready descriptors */
#ifdef DEBUG
      gettimeofday(&now,NULL);
      fprintf(stderr,"erl_call: debug got select at %ld.%06ld\n",
	      now.tv_sec,now.tv_usec);
#endif
      if (FD_ISSET(sockd,&rdset)) {
	if ((fd = accept(sockd,(struct sockaddr *)&peer,&len)) < 0)
	  return ERL_SYS_ERROR;
	
	/* now get sign-on message and terminate it */
#if defined(__WIN32__)
	if ((n=recv(fd,buf,16,0)) >= 0) buf[n]=0x0;
	closesocket(fd);
#else
	if ((n=read(fd,buf,16)) >= 0) buf[n]=0x0;
	close(fd);
#endif
#ifdef DEBUG
	fprintf(stderr,"erl_call: debug got %d, expected %d\n",
		atoi(buf),magic);
#endif
      
	if (atoi(buf) == magic) return 0; /* success */
      } /* if FD_SET */
    } /* switch */
  } /* while */

  /* unreached? */
  return ERL_SYS_ERROR;
} /* wait_for_erlang() */


static struct in_addr *get_addr(const char *hostname, struct in_addr *oaddr)
{
  struct hostent *hp;

#if !defined (__WIN32__)
  char buf[1024];
  struct hostent host;
  int herror;

  hp = ei_gethostbyname_r(hostname,&host,buf,1024,&herror);
#else
  hp = ei_gethostbyname(hostname);
#endif

  if (hp) {
    memmove(oaddr,hp->h_addr_list[0],sizeof(oaddr));
    return oaddr;
  }
  return NULL;
}
