/* * %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 ); }