/*
* %CopyrightBegin%
*
* Copyright Ericsson AB 1998-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%
*/
/*
* 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>
wchar_t *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 L"\\releases"
#define ERTS_SUBDIR_PREFIX L"\\erts-"
#define BIN_SUBDIR L"\\bin"
#define REGISTRY_BASE L"Software\\Ericsson\\Erlang\\"
#define DEFAULT_DATAFILE L"start_erl.data"
/* Global variables holding option values and command lines */
wchar_t *CommandLineStart = NULL;
wchar_t *ErlCommandLine = NULL;
wchar_t *MyCommandLine = NULL;
wchar_t *DataFileName = NULL;
wchar_t *RelDir = NULL;
wchar_t *BootFlagsFile = NULL;
wchar_t *BootFlags = NULL;
wchar_t *RegistryKey = NULL;
wchar_t *BinDir = NULL;
wchar_t *RootDir = NULL;
wchar_t *VsnDir = NULL;
wchar_t *Version = NULL;
wchar_t *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"
" {-rootdir <erlang root directory> | \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)
{
wchar_t *cmdline = CommandLineStart;
progname=cmdline;
/* Remove the first (quoted) string (our program name) */
if(*cmdline == L'"') {
cmdline++; /* Skip " */
while( (*cmdline != L'\0') && (*cmdline++) != L'"' )
;
} else {
while( (*cmdline != L'\0') && (*cmdline++) != L' ' )
;
}
while( (*cmdline) == L' ' )
cmdline++;
if( *cmdline == L'\0') {
ErlCommandLine = L"";
MyCommandLine = L"";
return;
}
cmdline[-1] = L'\0';
/* Start from the end of the string and search for "++ "
(PLUS PLUS SPACE) */
ErlCommandLine = cmdline;
if(wcsncmp(cmdline,L"++ ",3))
cmdline = wcsstr(cmdline,L" ++ ");
if( cmdline == NULL ) {
MyCommandLine = L"";
return;
}
/* Terminate the ErlCommandLine where MyCommandLine starts */
*cmdline++ = '\0';
/* Skip 'whitespace--whitespace' (WHITESPACE MINUS MINUS WHITESPACE) */
while( (*cmdline) == L' ' )
cmdline++;
while( (*cmdline) == L'+' )
cmdline++;
while( (*cmdline) == L' ' )
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.
*/
wchar_t * unquote_optionarg(wchar_t *str, wchar_t **strp)
{
/* This one is realloc:ed later */
wchar_t *newstr = (wchar_t *)malloc((wcslen(str)+1)*sizeof(wchar_t));
int i = 0, inquote = 0;
assert(newstr);
assert(str);
/* Skip leading spaces */
while( *str == L' ' )
str++;
/* Loop while in quote or until EOS or unquoted space
*/
while( (inquote==1) || ( (*str!=0) && (*str!=L' ') ) ) {
switch( *str ) {
case L'\\':
/* If we are inside a quoted string we should convert \c to c */
if( inquote && str[1] == L'"' )
str++;
newstr[i++]=*str++;
break;
case L'"':
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 = (wchar_t *)realloc(newstr, i*sizeof(wchar_t));
assert(newstr);
return(newstr);
}
/*
* Parses MyCommandLine and tries to fill in all the required option
* variables (in one way or another).
*/
void parse_commandline(void)
{
wchar_t *cmdline = MyCommandLine;
while( *cmdline != L'\0' ) {
switch( *cmdline ) {
case '-': /* Handle both -arg and /arg */
case '/':
*cmdline++;
if( _wcsnicmp(cmdline, L"data", 4) == 0) {
DataFileName = unquote_optionarg(cmdline+4, &cmdline);
} else if( _wcsnicmp(cmdline, L"rootdir", 7) == 0) {
RootDir = unquote_optionarg(cmdline+7, &cmdline);
#ifdef _DEBUG
fprintf(stderr, "RootDir: '%S'\n", RootDir);
#endif
} else if( _wcsnicmp(cmdline, L"reldir", 6) == 0) {
RelDir = unquote_optionarg(cmdline+6, &cmdline);
#ifdef _DEBUG
fprintf(stderr, "RelDir: '%S'\n", RelDir);
#endif
} else if( _wcsnicmp(cmdline, L"bootflags", 9) == 0) {
BootFlagsFile = unquote_optionarg(cmdline+9, &cmdline);
} else if( _wcsnicmp(cmdline, L"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;
wchar_t *newname;
long size;
char *ver;
char *rel;
if(!DataFileName){
DataFileName = malloc((wcslen(DEFAULT_DATAFILE) + 1)*sizeof(wchar_t));
wcscpy(DataFileName,DEFAULT_DATAFILE);
}
/* Is DataFileName relative or absolute ? */
if( (DataFileName[0] != L'\\') && (wcsncmp(DataFileName+1, L":\\", 2)!=0) ) {
/* Relative name, we have to prepend RelDir to it. */
if( !RelDir ) {
exit_help("Need -reldir when -data filename has relative path.");
} else {
size = (wcslen(DataFileName)+wcslen(RelDir)+2);
newname = (wchar_t *)malloc(size*sizeof(wchar_t));
assert(newname);
swprintf(newname, size, L"%s\\%s", RelDir, DataFileName);
free(DataFileName);
DataFileName=newname;
}
}
#ifdef _DEBUG
fprintf(stderr, "DataFileName: '%S'\n", DataFileName);
#endif
if( (fp=_wfopen(DataFileName, L"rb")) == NULL) {
exit_help("Cannot find the datafile.");
}
fseek(fp, 0, SEEK_END);
size=ftell(fp);
fseek(fp, 0, SEEK_SET);
ver = (char *)malloc(size+1);
rel = (char *)malloc(size+1);
assert(ver);
assert(rel);
if( (fscanf(fp, "%s %s", ver, rel)) == 0) {
fclose(fp);
exit_help("Format error in datafile.");
}
fclose(fp);
size = MultiByteToWideChar(CP_UTF8, 0, ver, -1, NULL, 0);
Version = malloc(size*sizeof(wchar_t));
assert(Version);
MultiByteToWideChar(CP_UTF8, 0, ver, -1, Version, size);
free(ver);
size = MultiByteToWideChar(CP_UTF8, 0, rel, -1, NULL, 0);
Release = malloc(size*sizeof(wchar_t));
assert(Release);
MultiByteToWideChar(CP_UTF8, 0, rel, -1, Release, size);
free(rel);
#ifdef _DEBUG
fprintf(stderr, "DataFile version: '%S'\n", Version);
fprintf(stderr, "DataFile release: '%S'\n", Release);
#endif
}
/*
* Read the bootflags. This file contains extra command line options to erl.exe
*/
void read_bootflags(void)
{
FILE *fp;
long fsize;
wchar_t *newname;
char *bootf;
if(BootFlagsFile) {
/* Is BootFlagsFile relative or absolute ? */
if( (BootFlagsFile[0] != L'\\') &&
(wcsncmp(BootFlagsFile+1, L":\\", 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 {
int len = wcslen(BootFlagsFile)+
wcslen(RelDir)+wcslen(Release)+3;
newname = (wchar_t *)malloc(len*sizeof(wchar_t));
assert(newname);
swprintf(newname, len, L"%s\\%s\\%s", RelDir, Release, BootFlagsFile);
free(BootFlagsFile);
BootFlagsFile=newname;
}
}
#ifdef _DEBUG
fprintf(stderr, "BootFlagsFile: '%S'\n", BootFlagsFile);
#endif
if( (fp=_wfopen(BootFlagsFile, L"rb")) == NULL) {
exit_help("Could not open BootFlags file.");
}
fseek(fp, 0, SEEK_END);
fsize=ftell(fp);
fseek(fp, 0, SEEK_SET);
bootf = (char *)malloc(fsize+1);
assert(bootf);
if( (fgets(bootf, fsize+1, fp)) == NULL) {
exit_help("Error while reading BootFlags file");
}
fclose(fp);
/* Adjust buffer size */
bootf = (char *)realloc(bootf, strlen(bootf)+1);
assert(bootf);
/* Strip \r\n from BootFlags */
fsize = strlen(bootf);
while( fsize > 0 &&
( (bootf[fsize-1] == '\r') ||
(bootf[fsize-1] == '\n') ) ) {
bootf[--fsize]=0;
}
fsize = MultiByteToWideChar(CP_UTF8, 0, bootf, -1, NULL, 0);
BootFlags = malloc(fsize*sizeof(wchar_t));
assert(BootFlags);
MultiByteToWideChar(CP_UTF8, 0, bootf, -1, BootFlags, fsize);
free(bootf);
} else {
/* Set empty BootFlags */
BootFlags = L"";
}
#ifdef _DEBUG
fprintf(stderr, "BootFlags: '%S'\n", BootFlags);
#endif
}
long start_new_node(void)
{
wchar_t *CommandLine;
unsigned long i;
STARTUPINFOW si;
DWORD dwExitCode;
i = wcslen(RelDir) + wcslen(Release) + 4;
VsnDir = (wchar_t *)malloc(i*sizeof(wchar_t));
assert(VsnDir);
swprintf(VsnDir, i, L"%s\\%s", RelDir, Release);
if( NoConfig ) {
i = wcslen(BinDir) + wcslen(ErlCommandLine) +
wcslen(BootFlags) + 64;
CommandLine = (wchar_t *)malloc(i*sizeof(wchar_t));
assert(CommandLine);
swprintf(CommandLine,
i,
L"\"%s\\erl.exe\" -boot \"%s\\start\" %s %s",
BinDir,
VsnDir,
ErlCommandLine,
BootFlags);
} else {
i = wcslen(BinDir) + wcslen(ErlCommandLine)
+ wcslen(BootFlags) + wcslen(VsnDir)*2 + 64;
CommandLine = (wchar_t *)malloc(i*sizeof(wchar_t));
assert(CommandLine);
swprintf(CommandLine,
i,
L"\"%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(STARTUPINFOW));
si.cb = sizeof(STARTUPINFOW);
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( (CreateProcessW(
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)
{
int len;
/* Try to find a descent RelDir */
if( !RelDir ) {
DWORD sz = 32;
while (1) {
DWORD nsz;
if (RelDir)
free(RelDir);
RelDir = malloc(sz*sizeof(wchar_t));
if (!RelDir) {
fprintf(stderr, "** Error : failed to allocate memory\n");
exit(1);
}
SetLastError(0);
nsz = GetEnvironmentVariableW(L"RELDIR", 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 (!RootDir) {
/* Impossible to find all data... */
exit_help("Need either Root directory nor Release directory.");
}
/* Ok, construct our own RelDir from RootDir */
sz = wcslen(RootDir)+wcslen(RELEASE_SUBDIR)+1;
RelDir = (wchar_t *) malloc(sz * sizeof(wchar_t));
assert(RelDir);
swprintf(RelDir, sz, L"%s" RELEASE_SUBDIR, RootDir);
read_datafile();
} else {
read_datafile();
}
} else {
read_datafile();
}
if( !RootDir ) {
/* Try to construct RootDir from RelDir */
wchar_t *p;
RootDir = malloc((wcslen(RelDir)+1)*sizeof(wchar_t));
wcscpy(RootDir,RelDir);
p = RootDir+wcslen(RootDir)-1;
if (p >= RootDir && (*p == L'/' || *p == L'\\'))
--p;
while (p >= RootDir && *p != L'/' && *p != L'\\')
--p;
if (p <= RootDir) { /* Empty RootDir is also an error */
exit_help("Cannot determine Root directory from "
"Release directory.");
}
*p = L'\0';
}
len = wcslen(RootDir)+wcslen(ERTS_SUBDIR_PREFIX)+
wcslen(Version)+wcslen(BIN_SUBDIR)+1;
BinDir = (wchar_t *) malloc(len * sizeof(wchar_t));
assert(BinDir);
swprintf(BinDir, len, L"%s" ERTS_SUBDIR_PREFIX L"%s" BIN_SUBDIR, RootDir, Version);
read_bootflags();
#ifdef _DEBUG
fprintf(stderr, "RelDir: '%S'\n", RelDir);
fprintf(stderr, "BinDir: '%S'\n", BinDir);
#endif
}
BOOL WINAPI LogoffHandlerRoutine( DWORD dwCtrlType )
{
if(dwCtrlType == CTRL_LOGOFF_EVENT) {
return TRUE;
}
if(dwCtrlType == CTRL_SHUTDOWN_EVENT) {
return TRUE;
}
return FALSE;
}
int main(void)
{
DWORD dwExitCode;
wchar_t *cmdline;
/* Make sure a logoff does not distrurb us. */
SetConsoleCtrlHandler(LogoffHandlerRoutine, TRUE);
cmdline = GetCommandLineW();
assert(cmdline);
CommandLineStart = (wchar_t *) malloc((wcslen(cmdline) + 1)*sizeof(wchar_t));
assert(CommandLineStart);
wcscpy(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 );
}