/* * %CopyrightBegin% * * Copyright Ericsson AB 1997-2016. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * %CopyrightEnd% * */ /* An exception from using eidef.h, use config.h directly */ #include "config.h" #include #include #include #ifdef __WIN32__ #include #include #include #elif VXWORKS #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include /* #include "netdb.h" */ #else /* other unix */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 #ifndef HAVE_SOCKLEN_T typedef int SocklenType; #else typedef socklen_t SocklenType; #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; SocklenType 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; SocklenType len = (SocklenType) 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; }