diff options
Diffstat (limited to 'erts/etc/win32/start_erl.c')
-rw-r--r-- | erts/etc/win32/start_erl.c | 677 |
1 files changed, 677 insertions, 0 deletions
diff --git a/erts/etc/win32/start_erl.c b/erts/etc/win32/start_erl.c new file mode 100644 index 0000000000..dcf8c8b281 --- /dev/null +++ b/erts/etc/win32/start_erl.c @@ -0,0 +1,677 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1998-2009. All Rights Reserved. + * + * 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 online 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. + * + * %CopyrightEnd% + */ +/* + * start_erl.c - Windows NT start_erl + * + * Author: Mattias Nilsson + */ + +#define WIN32_LEAN_AND_MEAN +#define STRICT +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <windows.h> +#include <assert.h> + +char *progname; + +/* + * If CASE_SENSITIVE_OPTIONS is specified, options are case sensitive + * (lower case). + * The reason for this switch is that _strnicmp is Microsoft specific. + */ +#ifndef CASE_SENSITIVE_OPTIONS +#define strnicmp strncmp +#else +#define strnicmp _strnicmp +#endif + +#define RELEASE_SUBDIR "\\releases" +#define REGISTRY_BASE "Software\\Ericsson\\Erlang\\" +#define DEFAULT_DATAFILE "start_erl.data" + +/* Global variables holding option values and command lines */ +char *CommandLineStart = NULL; +char *ErlCommandLine = NULL; +char *MyCommandLine = NULL; +char *DataFileName = NULL; +char *RelDir = NULL; +char *BootFlagsFile = NULL; +char *BootFlags = NULL; +char *RegistryKey = NULL; +char *BinDir = NULL; +char *RootDir = NULL; +char *VsnDir = NULL; +char *Version = NULL; +char *Release = NULL; +BOOL NoConfig=FALSE; +PROCESS_INFORMATION ErlProcessInfo; + +/* + * Error reason printout 'the microsoft way' + */ +void ShowLastError(void) +{ + LPVOID lpMsgBuf; + DWORD dwErr; + + dwErr = GetLastError(); + if( dwErr == ERROR_SUCCESS ) + return; + + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + dwErr, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &lpMsgBuf, + 0, + NULL + ); + fprintf(stderr, lpMsgBuf); + LocalFree( lpMsgBuf ); +} + +/* + * Exit the program and give a nice and firm explanation of why + * and how you can avoid it. + */ +void exit_help(char *err) +{ + ShowLastError(); + fprintf(stderr, "** Error: %s\n", err); + + printf("Usage:\n%s\n" + " [<erlang options>] ++\n" + " [-data <datafile>]\n" + " [-reldir <releasedir>]\n" + " [-bootflags <bootflagsfile>]\n" + " [-noconfig]\n", progname); + + exit(0); +} + + +/* + * Splits the command line into two strings: + * 1. Options to the Erlang node (ErlCommandLine) + * 2. Options to this program (MyCommandLine) + */ +void split_commandline(void) +{ + char *cmdline = CommandLineStart; + + progname=cmdline; + + /* Remove the first (quoted) string (our program name) */ + if(*cmdline == '"') { + cmdline++; /* Skip " */ + while( (*cmdline != '\0') && (*cmdline++) != '"' ) + ; + } else { + while( (*cmdline != '\0') && (*cmdline++) != ' ' ) + ; + } + + while( (*cmdline) == ' ' ) + cmdline++; + + if( *cmdline == '\0') { + ErlCommandLine = ""; + MyCommandLine = ""; + return; + } + + cmdline[-1] = '\0'; + + /* Start from the end of the string and search for "++ " + (PLUS PLUS SPACE) */ + ErlCommandLine = cmdline; + if(strncmp(cmdline,"++ ",3)) + cmdline = strstr(cmdline," ++ "); + if( cmdline == NULL ) { + MyCommandLine = ""; + return; + } + /* Terminate the ErlCommandLine where MyCommandLine starts */ + *cmdline++ = '\0'; + + /* Skip 'whitespace--whitespace' (WHITESPACE MINUS MINUS WHITESPACE) */ + while( (*cmdline) == ' ' ) + cmdline++; + while( (*cmdline) == '+' ) + cmdline++; + while( (*cmdline) == ' ' ) + cmdline++; + + MyCommandLine = cmdline; + +#ifdef _DEBUG + fprintf(stderr, "ErlCommandLine: '%s'\n", ErlCommandLine); + fprintf(stderr, "MyCommandLine: '%s'\n", MyCommandLine); +#endif +} + + +/* + * 'Smart' unquoting of a string: \" becomes " and " becomes (nothing) + * Skips any leading spaces and parses up to NULL or end of quoted string. + * Calls exit_help() if an unterminated quote is detected. + */ +char * unquote_optionarg(char *str, char **strp) +{ + char *newstr = (char *)malloc(strlen(str)+1); /* This one is realloc:ed later */ + int i=0, inquote=0; + + assert(newstr); + assert(str); + + /* Skip leading spaces */ + while( *str == ' ' ) + str++; + + /* Loop while in quote or until EOS or unquoted space + */ + while( (inquote==1) || ( (*str!=0) && (*str!=' ') ) ) { + switch( *str ) { + case '\\': + /* If we are inside a quoted string we should convert \c to c */ + if( inquote && str[1] == '"' ) + str++; + newstr[i++]=*str++; + break; + case '"': + inquote = 1-inquote; + *str++; + break; + default: + newstr[i++]=*str++; + break; + } + + if( (*str == 0) && (inquote==1) ) { + exit_help("Unterminated quote."); + } + } + newstr[i++] = 0x00; + + /* Update the supplied pointer (used for continued parsing of options) */ + *strp = str; + + /* Adjust memblock of newstr */ + newstr = (char *)realloc(newstr, i); + assert(newstr); + return(newstr); +} + + +/* + * Parses MyCommandLine and tries to fill in all the required option variables + * (one way or another). + */ +void parse_commandline(void) +{ + char *cmdline = MyCommandLine; + + while( *cmdline != '\0' ) { + switch( *cmdline ) { + case '-': /* Handle both -arg and /arg */ + case '/': + *cmdline++; + if( strnicmp(cmdline, "data", 4) == 0) { + DataFileName = unquote_optionarg(cmdline+4, &cmdline); + } else if( strnicmp(cmdline, "reldir", 6) == 0) { + RelDir = unquote_optionarg(cmdline+6, &cmdline); +#ifdef _DEBUG + fprintf(stderr, "RelDir: '%s'\n", RelDir); +#endif + } else if( strnicmp(cmdline, "bootflags", 9) == 0) { + BootFlagsFile = unquote_optionarg(cmdline+9, &cmdline); + } else if( strnicmp(cmdline, "noconfig", 8) == 0) { + NoConfig=TRUE; +#ifdef _DEBUG + fprintf(stderr, "NoConfig=TRUE\n"); +#endif + } else { + fprintf(stderr, "Unkown option: '%s'\n", cmdline); + exit_help("Unknown command line option"); + } + break; + default: + cmdline++; + break; + } + } +} + + +/* + * Read the data file specified and get the version and release number + * from it. + * + * This function also construct the correct RegistryKey from the version information + * retrieved. + */ +void read_datafile(void) +{ + FILE *fp; + char *newname; + long size; + + if(!DataFileName){ + DataFileName = malloc(strlen(DEFAULT_DATAFILE) + 1); + strcpy(DataFileName,DEFAULT_DATAFILE); + } + /* Is DataFileName relative or absolute ? */ + if( (DataFileName[0] != '\\') && (strncmp(DataFileName+1, ":\\", 2)!=0) ) { + /* Relative name, we have to prepend RelDir to it. */ + if( !RelDir ) { + exit_help("Need -reldir when -data filename has relative path."); + } else { + newname = (char *)malloc(strlen(DataFileName)+strlen(RelDir)+2); + assert(newname); + sprintf(newname, "%s\\%s", RelDir, DataFileName); + free(DataFileName); + DataFileName=newname; + } + } + +#ifdef _DEBUG + fprintf(stderr, "DataFileName: '%s'\n", DataFileName); +#endif + + if( (fp=fopen(DataFileName, "rb")) == NULL) { + exit_help("Cannot find the datafile."); + } + + fseek(fp, 0, SEEK_END); + size=ftell(fp); + fseek(fp, 0, SEEK_SET); + + Version = (char *)malloc(size+1); + Release = (char *)malloc(size+1); + assert(Version); + assert(Release); + + if( (fscanf(fp, "%s %s", Version, Release)) == 0) { + fclose(fp); + exit_help("Format error in datafile."); + } + + fclose(fp); + +#ifdef _DEBUG + fprintf(stderr, "DataFile version: '%s'\n", Version); + fprintf(stderr, "DataFile release: '%s'\n", Release); +#endif +} + + +/* + * Read the registry keys we need + */ +void read_registry_keys(void) +{ + HKEY hReg; + ULONG lLen; + + /* Create the RegistryKey name */ + RegistryKey = (char *) malloc(strlen(REGISTRY_BASE) + + strlen(Version) + 1); + assert(RegistryKey); + sprintf(RegistryKey, REGISTRY_BASE "%s", Version); + + /* We always need to find BinDir */ + if( (RegOpenKeyEx(HKEY_LOCAL_MACHINE, + RegistryKey, + 0, + KEY_READ, + &hReg)) != ERROR_SUCCESS ) { + exit_help("Could not open registry key."); + } + + /* First query size of data */ + if( (RegQueryValueEx(hReg, + "Bindir", + NULL, + NULL, + NULL, + &lLen)) != ERROR_SUCCESS) { + exit_help("Failed to query BinDir of release.\n"); + } + + /* Allocate enough space */ + BinDir = (char *)malloc(lLen+1); + assert(BinDir); + /* Retrieve the value */ + if( (RegQueryValueEx(hReg, + "Bindir", + NULL, + NULL, + (unsigned char *) BinDir, + &lLen)) != ERROR_SUCCESS) { + exit_help("Failed to query BinDir of release (2).\n"); + } + +#ifdef _DEBUG + fprintf(stderr, "Bindir: '%s'\n", BinDir); +#endif + + /* We also need the rootdir, in case we need to build RelDir later */ + + /* First query size of data */ + if( (RegQueryValueEx(hReg, + "Rootdir", + NULL, + NULL, + NULL, + &lLen)) != ERROR_SUCCESS) { + exit_help("Failed to query RootDir of release.\n"); + } + + /* Allocate enough space */ + RootDir = (char *) malloc(lLen+1); + assert(RootDir); + /* Retrieve the value */ + if( (RegQueryValueEx(hReg, + "Rootdir", + NULL, + NULL, + (unsigned char *) RootDir, + &lLen)) != ERROR_SUCCESS) { + exit_help("Failed to query RootDir of release (2).\n"); + } + +#ifdef _DEBUG + fprintf(stderr, "Rootdir: '%s'\n", RootDir); +#endif + + RegCloseKey(hReg); +} + +/* + * Read the bootflags. This file contains extra command line options to erl.exe + */ +void read_bootflags(void) +{ + FILE *fp; + long fsize; + char *newname; + + if(BootFlagsFile) { + /* Is BootFlagsFile relative or absolute ? */ + if( (BootFlagsFile[0] != '\\') && + (strncmp(BootFlagsFile+1, ":\\", 2)!=0) ) { + /* Relative name, we have to prepend RelDir\\Version to it. */ + if( !RelDir ) { + exit_help("Need -reldir when -bootflags " + "filename has relative path."); + } else { + newname = (char *)malloc(strlen(BootFlagsFile)+strlen(RelDir)+strlen(Release)+3); + assert(newname); + sprintf(newname, "%s\\%s\\%s", RelDir, Release, BootFlagsFile); + free(BootFlagsFile); + BootFlagsFile=newname; + } + } + +#ifdef _DEBUG + fprintf(stderr, "BootFlagsFile: '%s'\n", BootFlagsFile); +#endif + + + + if( (fp=fopen(BootFlagsFile, "rb")) == NULL) { + exit_help("Could not open BootFlags file."); + } + + fseek(fp, 0, SEEK_END); + fsize=ftell(fp); + fseek(fp, 0, SEEK_SET); + + BootFlags = (char *)malloc(fsize+1); + assert(BootFlags); + if( (fgets(BootFlags, fsize+1, fp)) == NULL) { + exit_help("Error while reading BootFlags file"); + } + fclose(fp); + + /* Adjust buffer size */ + BootFlags = (char *)realloc(BootFlags, strlen(BootFlags)+1); + assert(BootFlags); + + /* Strip \r\n from BootFlags */ + fsize = strlen(BootFlags); + while( fsize > 0 && + ( (BootFlags[fsize-1] == '\r') || + (BootFlags[fsize-1] == '\n') ) ) { + BootFlags[--fsize]=0; + } + + } else { + /* Set empty BootFlags */ + BootFlags = ""; + } + +#ifdef _DEBUG + fprintf(stderr, "BootFlags: '%s'\n", BootFlags); +#endif +} + + +long start_new_node(void) +{ + char *CommandLine; + unsigned long i; + STARTUPINFO si; + DWORD dwExitCode; + + i = strlen(RelDir) + strlen(Release) + 4; + VsnDir = (char *)malloc(i); + assert(VsnDir); + sprintf(VsnDir, "%s\\%s", RelDir, Release); + + if( NoConfig ) { + i = strlen(BinDir) + strlen(ErlCommandLine) + + strlen(BootFlags) + 64; + CommandLine = (char *)malloc(i); + assert(CommandLine); + sprintf(CommandLine, + "\"%s\\erl.exe\" -boot \"%s\\start\" %s %s", + BinDir, + VsnDir, + ErlCommandLine, + BootFlags); + } else { + i = strlen(BinDir) + strlen(ErlCommandLine) + + strlen(BootFlags) + strlen(VsnDir)*2 + 64; + CommandLine = (char *)malloc(i); + assert(CommandLine); + sprintf(CommandLine, + "\"%s\\erl.exe\" -boot \"%s\\start\" -config \"%s\\sys\" %s %s", + BinDir, + VsnDir, + VsnDir, + ErlCommandLine, + BootFlags); + } + +#ifdef _DEBUG + fprintf(stderr, "CommandLine: '%s'\n", CommandLine); +#endif + + /* Initialize the STARTUPINFO structure. */ + memset(&si, 0, sizeof(STARTUPINFO)); + si.cb = sizeof(STARTUPINFO); + si.lpTitle = NULL; + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + si.hStdError = GetStdHandle(STD_ERROR_HANDLE); + + /* Create the new Erlang process */ + if( (CreateProcess( + NULL, /* pointer to name of executable module */ + CommandLine, /* pointer to command line string */ + NULL, /* pointer to process security attributes */ + NULL, /* pointer to thread security attributes */ + TRUE, /* handle inheritance flag */ + GetPriorityClass(GetCurrentProcess()), + /* creation flags */ + NULL, /* pointer to new environment block */ + BinDir,/* pointer to current directory name */ + &si, /* pointer to STARTUPINFO */ + &ErlProcessInfo /* pointer to PROCESS_INFORMATION */ + )) == FALSE) { + ShowLastError(); + exit_help("Failed to start new node"); + } + +#ifdef _DEBUG + fprintf(stderr, "Waiting for Erlang to terminate.\n"); +#endif + if(MsgWaitForMultipleObjects(1,&ErlProcessInfo.hProcess, FALSE, + INFINITE, QS_POSTMESSAGE) == WAIT_OBJECT_0+1){ + if(PostThreadMessage(ErlProcessInfo.dwThreadId, + WM_USER, + (WPARAM) 0, + (LPARAM) 0)){ + /* Wait 10 seconds for erl process to die, elsee terminate it. */ + if(WaitForSingleObject(ErlProcessInfo.hProcess, 10000) + != WAIT_OBJECT_0){ + TerminateProcess(ErlProcessInfo.hProcess,0); + } + } else { + TerminateProcess(ErlProcessInfo.hProcess,0); + } + } + GetExitCodeProcess(ErlProcessInfo.hProcess, &dwExitCode); +#ifdef _DEBUG + fprintf(stderr, "Erlang terminated.\n"); +#endif + + free(CommandLine); + return(dwExitCode); +} + + +/* + * Try to make the needed options complete by looking through the data file, + * environment variables and registry entries. + */ +void complete_options(void) +{ + /* Try to find a descent RelDir */ + if( !RelDir ) { + DWORD sz = 32; + while (1) { + DWORD nsz; + if (RelDir) + free(RelDir); + RelDir = malloc(sz); + if (!RelDir) { + fprintf(stderr, "** Error : failed to allocate memory\n"); + exit(1); + } + SetLastError(0); + nsz = GetEnvironmentVariable((LPCTSTR) "RELDIR", + (LPTSTR) RelDir, + sz); + if (nsz == 0 && GetLastError() == ERROR_ENVVAR_NOT_FOUND) { + free(RelDir); + RelDir = NULL; + break; + } + else if (nsz <= sz) + break; + else + sz = nsz; + } + if (RelDir == NULL) { + if(DataFileName){ + /* Needs to be absolute for this to work, but we + can try... */ + read_datafile(); + read_registry_keys(); + } else { + /* Impossible to find all data... */ + exit_help("Need either Release directory or an absolute " + "datafile name."); + } + /* Ok, construct our own RelDir from RootDir */ + RelDir = (char *) malloc(strlen(RootDir)+strlen(RELEASE_SUBDIR)+1); + assert(RelDir); + sprintf(RelDir, "%s" RELEASE_SUBDIR, RootDir); + } else { + read_datafile(); + read_registry_keys(); + } + } else { + read_datafile(); + read_registry_keys(); + } + read_bootflags(); + +#ifdef _DEBUG + fprintf(stderr, "RelDir: '%s'\n", RelDir); +#endif +} + + + + +BOOL WINAPI LogoffHandlerRoutine( DWORD dwCtrlType ) +{ + if(dwCtrlType == CTRL_LOGOFF_EVENT) { + return TRUE; + } + + return FALSE; +} + + + + +int main(void) +{ + DWORD dwExitCode; + char *cmdline; + + /* Make sure a logoff does not distrurb us. */ + SetConsoleCtrlHandler(LogoffHandlerRoutine, TRUE); + + cmdline = GetCommandLine(); + assert(cmdline); + + CommandLineStart = (char *) malloc(strlen(cmdline) + 1); + assert(CommandLineStart); + strcpy(CommandLineStart,cmdline); + + split_commandline(); + parse_commandline(); + complete_options(); + + /* We now have all the options we need in order to fire up a new node.. */ + dwExitCode = start_new_node(); + + return( (int) dwExitCode ); +} + + |