1 /*
2  * %CopyrightBegin%
3  *
4  * Copyright Ericsson AB 1998-2016. All Rights Reserved.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  *
18  * %CopyrightEnd%
19  */
20 /*
21  * start_erl.c - Windows NT start_erl
22  *
23  * Author: Mattias Nilsson
24  */
25 
26 #define WIN32_LEAN_AND_MEAN
27 #define STRICT
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <windows.h>
32 #include <assert.h>
33 
34 wchar_t *progname;
35 
36 /*
37  * If CASE_SENSITIVE_OPTIONS is specified, options are case sensitive
38  * (lower case).
39  * The reason for this switch is that _strnicmp is Microsoft specific.
40  */
41 #ifndef CASE_SENSITIVE_OPTIONS
42 #define strnicmp strncmp
43 #else
44 #define strnicmp _strnicmp
45 #endif
46 
47 #define RELEASE_SUBDIR L"\\releases"
48 #define ERTS_SUBDIR_PREFIX L"\\erts-"
49 #define BIN_SUBDIR L"\\bin"
50 #define REGISTRY_BASE L"Software\\Ericsson\\Erlang\\"
51 #define DEFAULT_DATAFILE L"start_erl.data"
52 
53 /* Global variables holding option values and command lines */
54 wchar_t *CommandLineStart = NULL;
55 wchar_t *ErlCommandLine = NULL;
56 wchar_t *MyCommandLine = NULL;
57 wchar_t *DataFileName = NULL;
58 wchar_t *RelDir = NULL;
59 wchar_t *BootFlagsFile = NULL;
60 wchar_t *BootFlags = NULL;
61 wchar_t *RegistryKey = NULL;
62 wchar_t *BinDir = NULL;
63 wchar_t *RootDir = NULL;
64 wchar_t *VsnDir = NULL;
65 wchar_t *Version = NULL;
66 wchar_t *Release = NULL;
67 BOOL NoConfig=FALSE;
68 PROCESS_INFORMATION ErlProcessInfo;
69 
70 /*
71  * Error reason printout 'the microsoft way'
72  */
ShowLastError(void)73 void ShowLastError(void)
74 {
75     LPVOID	lpMsgBuf;
76     DWORD	dwErr;
77 
78     dwErr = GetLastError();
79     if( dwErr == ERROR_SUCCESS )
80 	return;
81 
82     FormatMessage(
83 		  FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
84 		  NULL,
85 		  dwErr,
86 		  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
87 		  (LPTSTR) &lpMsgBuf,
88 		  0,
89 		  NULL
90 		  );
91     fprintf(stderr, lpMsgBuf);
92     LocalFree( lpMsgBuf );
93 }
94 
95 /*
96  * Exit the program and give a nice and firm explanation of why
97  * and how you can avoid it.
98  */
exit_help(char * err)99 void exit_help(char *err)
100 {
101     ShowLastError();
102     fprintf(stderr, "** Error: %s\n", err);
103 
104     printf("Usage:\n%S\n"
105 	   "      [<erlang options>] ++\n"
106 	   "      [-data <datafile>]\n"
107 	   "      {-rootdir <erlang root directory> | \n"
108 	   "       -reldir <releasedir>}\n"
109 	   "      [-bootflags <bootflagsfile>]\n"
110 	   "      [-noconfig]\n", progname);
111 
112     exit(0);
113 }
114 
115 
116 /*
117  * Splits the command line into two strings:
118  * 1. Options to the Erlang node (ErlCommandLine)
119  * 2. Options to this program (MyCommandLine)
120  */
split_commandline(void)121 void split_commandline(void)
122 {
123     wchar_t	*cmdline = CommandLineStart;
124 
125     progname=cmdline;
126 
127     /* Remove the first (quoted) string (our program name) */
128     if(*cmdline == L'"') {
129 	cmdline++;         /* Skip " */
130 	while( (*cmdline != L'\0') && (*cmdline++) != L'"' )
131 	    ;
132     } else {
133 	while( (*cmdline != L'\0') && (*cmdline++) != L' ' )
134 	    ;
135     }
136 
137     while( (*cmdline) == L' ' )
138 	cmdline++;
139 
140     if( *cmdline == L'\0') {
141 	ErlCommandLine = L"";
142 	MyCommandLine = L"";
143 	return;
144     }
145 
146     cmdline[-1] = L'\0';
147 
148     /* Start from the end of the string and search for "++ "
149        (PLUS PLUS SPACE) */
150     ErlCommandLine = cmdline;
151     if(wcsncmp(cmdline,L"++ ",3))
152 	cmdline = wcsstr(cmdline,L" ++ ");
153     if( cmdline == NULL ) {
154 	MyCommandLine = L"";
155 	return;
156     }
157     /* Terminate the ErlCommandLine where MyCommandLine starts */
158     *cmdline++ = '\0';
159 
160     /* Skip 'whitespace--whitespace' (WHITESPACE MINUS MINUS WHITESPACE) */
161     while( (*cmdline) == L' ' )
162 	cmdline++;
163     while( (*cmdline) == L'+' )
164 	cmdline++;
165     while( (*cmdline) == L' ' )
166 	cmdline++;
167 
168     MyCommandLine = cmdline;
169 
170 #ifdef _DEBUG
171     fprintf(stderr, "ErlCommandLine: '%S'\n", ErlCommandLine);
172     fprintf(stderr, "MyCommandLine:  '%S'\n", MyCommandLine);
173 #endif
174 }
175 
176 
177 /*
178  * 'Smart' unquoting of a string: \" becomes " and " becomes  (nothing)
179  * Skips any leading spaces and parses up to NULL or end of quoted string.
180  * Calls exit_help() if an unterminated quote is detected.
181  */
unquote_optionarg(wchar_t * str,wchar_t ** strp)182 wchar_t * unquote_optionarg(wchar_t *str, wchar_t **strp)
183 {
184     /* This one is realloc:ed later */
185     wchar_t	*newstr = (wchar_t *)malloc((wcslen(str)+1)*sizeof(wchar_t));
186     int		i = 0, inquote = 0;
187 
188     assert(newstr);
189     assert(str);
190 
191     /* Skip leading spaces */
192     while( *str == L' ' )
193 	str++;
194 
195     /* Loop while in quote or until EOS or unquoted space
196      */
197     while( (inquote==1)  || ( (*str!=0) && (*str!=L' ') )  ) {
198 	switch( *str ) {
199 	case L'\\':
200 	    /* If we are inside a quoted string we should convert \c to c */
201 	    if( inquote  && str[1] == L'"' )
202 		str++;
203 	    newstr[i++]=*str++;
204 	    break;
205 	case L'"':
206 	    inquote = 1-inquote;
207 	    *str++;
208 	    break;
209 	default:
210 	    newstr[i++]=*str++;
211 	    break;
212 	}
213 
214 	if( (*str == 0) && (inquote==1) ) {
215 	    exit_help("Unterminated quote.");
216 	}
217     }
218     newstr[i++] = 0x00;
219 
220     /* Update the supplied pointer (used for continued parsing of options) */
221     *strp = str;
222 
223     /* Adjust memblock of newstr */
224     newstr = (wchar_t *)realloc(newstr, i*sizeof(wchar_t));
225     assert(newstr);
226     return(newstr);
227 }
228 
229 
230 /*
231  * Parses MyCommandLine and tries to fill in all the required option
232  * variables (in one way or another).
233  */
parse_commandline(void)234 void parse_commandline(void)
235 {
236     wchar_t *cmdline = MyCommandLine;
237 
238     while( *cmdline != L'\0' ) {
239 	switch( *cmdline ) {
240 	case '-':		/* Handle both -arg and /arg */
241 	case '/':
242 	    *cmdline++;
243 	    if( _wcsnicmp(cmdline, L"data", 4) == 0) {
244 		DataFileName = unquote_optionarg(cmdline+4, &cmdline);
245 	    } else if( _wcsnicmp(cmdline, L"rootdir", 7) == 0) {
246 		RootDir = unquote_optionarg(cmdline+7, &cmdline);
247 #ifdef _DEBUG
248 		fprintf(stderr, "RootDir: '%S'\n", RootDir);
249 #endif
250 	    } else if( _wcsnicmp(cmdline, L"reldir", 6) == 0) {
251 		RelDir = unquote_optionarg(cmdline+6, &cmdline);
252 #ifdef _DEBUG
253 		fprintf(stderr, "RelDir: '%S'\n", RelDir);
254 #endif
255 	    } else if( _wcsnicmp(cmdline, L"bootflags", 9) == 0) {
256 		BootFlagsFile = unquote_optionarg(cmdline+9, &cmdline);
257 	    } else if( _wcsnicmp(cmdline, L"noconfig", 8) == 0) {
258 		NoConfig=TRUE;
259 #ifdef _DEBUG
260 		fprintf(stderr, "NoConfig=TRUE\n");
261 #endif
262 	    } else {
263 		fprintf(stderr, "Unkown option: '%S'\n", cmdline);
264 		exit_help("Unknown command line option");
265 	    }
266 	    break;
267 	default:
268 	    cmdline++;
269 	    break;
270 	}
271     }
272 }
273 
274 
275 /*
276  * Read the data file specified and get the version and release number
277  * from it.
278  *
279  * This function also construct the correct RegistryKey from the version
280  * information retrieved.
281  */
read_datafile(void)282 void read_datafile(void)
283 {
284     FILE	*fp;
285     wchar_t	*newname;
286     long	size;
287     char        *ver;
288     char        *rel;
289 
290     if(!DataFileName){
291 	DataFileName = malloc((wcslen(DEFAULT_DATAFILE) + 1)*sizeof(wchar_t));
292 	wcscpy(DataFileName,DEFAULT_DATAFILE);
293     }
294     /* Is DataFileName relative or absolute ? */
295     if( (DataFileName[0] != L'\\') && (wcsncmp(DataFileName+1, L":\\", 2)!=0) ) {
296 	/* Relative name, we have to prepend RelDir to it. */
297 	if( !RelDir ) {
298 	    exit_help("Need -reldir when -data filename has relative path.");
299 	} else {
300 	    size = (wcslen(DataFileName)+wcslen(RelDir)+2);
301 	    newname = (wchar_t *)malloc(size*sizeof(wchar_t));
302 	    assert(newname);
303 	    swprintf(newname, size, L"%s\\%s", RelDir, DataFileName);
304 	    free(DataFileName);
305 	    DataFileName=newname;
306 	}
307     }
308 
309 #ifdef _DEBUG
310     fprintf(stderr, "DataFileName: '%S'\n", DataFileName);
311 #endif
312 
313     if( (fp=_wfopen(DataFileName, L"rb")) == NULL) {
314 	exit_help("Cannot find the datafile.");
315     }
316 
317     fseek(fp, 0, SEEK_END);
318     size=ftell(fp);
319     fseek(fp, 0, SEEK_SET);
320 
321     ver = (char *)malloc(size+1);
322     rel = (char *)malloc(size+1);
323     assert(ver);
324     assert(rel);
325 
326     if( (fscanf(fp, "%s %s", ver, rel)) == 0) {
327 	fclose(fp);
328 	exit_help("Format error in datafile.");
329     }
330 
331     fclose(fp);
332 
333     size = MultiByteToWideChar(CP_UTF8, 0, ver, -1, NULL, 0);
334     Version = malloc(size*sizeof(wchar_t));
335     assert(Version);
336     MultiByteToWideChar(CP_UTF8, 0, ver, -1, Version, size);
337     free(ver);
338 
339     size = MultiByteToWideChar(CP_UTF8, 0, rel, -1, NULL, 0);
340     Release = malloc(size*sizeof(wchar_t));
341     assert(Release);
342     MultiByteToWideChar(CP_UTF8, 0, rel, -1, Release, size);
343     free(rel);
344 
345 #ifdef _DEBUG
346     fprintf(stderr, "DataFile version: '%S'\n", Version);
347     fprintf(stderr, "DataFile release: '%S'\n", Release);
348 #endif
349 }
350 
351 
352 /*
353  * Read the bootflags. This file contains extra command line options to erl.exe
354  */
read_bootflags(void)355 void read_bootflags(void)
356 {
357     FILE	*fp;
358     long	fsize;
359     wchar_t	*newname;
360     char        *bootf;
361 
362     if(BootFlagsFile) {
363 	/* Is BootFlagsFile relative or absolute ? */
364 	if( (BootFlagsFile[0] != L'\\') &&
365 	    (wcsncmp(BootFlagsFile+1, L":\\", 2)!=0) ) {
366 	    /* Relative name, we have to prepend RelDir\\Version to it. */
367 	    if( !RelDir ) {
368 		exit_help("Need -reldir when -bootflags "
369 			  "filename has relative path.");
370 	    } else {
371 		int len = wcslen(BootFlagsFile)+
372 		    wcslen(RelDir)+wcslen(Release)+3;
373 		newname = (wchar_t *)malloc(len*sizeof(wchar_t));
374 		assert(newname);
375 		swprintf(newname, len, L"%s\\%s\\%s", RelDir, Release, BootFlagsFile);
376 		free(BootFlagsFile);
377 		BootFlagsFile=newname;
378 	    }
379 	}
380 
381 #ifdef _DEBUG
382 	fprintf(stderr, "BootFlagsFile: '%S'\n", BootFlagsFile);
383 #endif
384 
385 	if( (fp=_wfopen(BootFlagsFile, L"rb")) == NULL) {
386 	    exit_help("Could not open BootFlags file.");
387 	}
388 
389 	fseek(fp, 0, SEEK_END);
390 	fsize=ftell(fp);
391 	fseek(fp, 0, SEEK_SET);
392 
393 	bootf = (char *)malloc(fsize+1);
394 	assert(bootf);
395 	if( (fgets(bootf, fsize+1, fp)) == NULL) {
396 	    exit_help("Error while reading BootFlags file");
397 	}
398 	fclose(fp);
399 
400 	/* Adjust buffer size */
401 	bootf = (char *)realloc(bootf, strlen(bootf)+1);
402 	assert(bootf);
403 
404 	/* Strip \r\n from BootFlags */
405 	fsize = strlen(bootf);
406 	while( fsize > 0 &&
407 	       ( (bootf[fsize-1] == '\r') ||
408 		(bootf[fsize-1] == '\n') ) ) {
409 	    bootf[--fsize]=0;
410 	}
411 	fsize = MultiByteToWideChar(CP_UTF8, 0, bootf, -1, NULL, 0);
412 	BootFlags = malloc(fsize*sizeof(wchar_t));
413 	assert(BootFlags);
414 	MultiByteToWideChar(CP_UTF8, 0, bootf, -1, BootFlags, fsize);
415 	free(bootf);
416     } else {
417 	/* Set empty BootFlags */
418 	BootFlags = L"";
419     }
420 
421 #ifdef _DEBUG
422     fprintf(stderr, "BootFlags: '%S'\n", BootFlags);
423 #endif
424 }
425 
426 
start_new_node(void)427 long start_new_node(void)
428 {
429     wchar_t			*CommandLine;
430     unsigned long		i;
431     STARTUPINFOW		si;
432     DWORD			dwExitCode;
433 
434     i = wcslen(RelDir) + wcslen(Release) + 4;
435     VsnDir = (wchar_t *)malloc(i*sizeof(wchar_t));
436     assert(VsnDir);
437     swprintf(VsnDir, i, L"%s\\%s", RelDir, Release);
438 
439     if( NoConfig ) {
440 	i = wcslen(BinDir) + wcslen(ErlCommandLine) +
441 	    wcslen(BootFlags) + 64;
442 	CommandLine = (wchar_t *)malloc(i*sizeof(wchar_t));
443 	assert(CommandLine);
444 	swprintf(CommandLine,
445 		 i,
446 		 L"\"%s\\erl.exe\" -boot \"%s\\start\" %s %s",
447 		 BinDir,
448 		 VsnDir,
449 		 ErlCommandLine,
450 		 BootFlags);
451     } else {
452 	i = wcslen(BinDir) + wcslen(ErlCommandLine)
453 	    + wcslen(BootFlags) + wcslen(VsnDir)*2 + 64;
454 	CommandLine = (wchar_t *)malloc(i*sizeof(wchar_t));
455 	assert(CommandLine);
456 	swprintf(CommandLine,
457 		 i,
458 		 L"\"%s\\erl.exe\" -boot \"%s\\start\" -config \"%s\\sys\" %s %s",
459 		 BinDir,
460 		 VsnDir,
461 		 VsnDir,
462 		 ErlCommandLine,
463 		 BootFlags);
464     }
465 
466 #ifdef _DEBUG
467     fprintf(stderr, "CommandLine: '%S'\n", CommandLine);
468 #endif
469 
470     /* Initialize the STARTUPINFO structure. */
471     memset(&si, 0, sizeof(STARTUPINFOW));
472     si.cb = sizeof(STARTUPINFOW);
473     si.lpTitle = NULL;
474     si.dwFlags = STARTF_USESTDHANDLES;
475     si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
476     si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
477     si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
478 
479     /* Create the new Erlang process */
480     if( (CreateProcessW(
481 			NULL,  /* pointer to name of executable module    */
482 			CommandLine,	/* pointer to command line string    */
483 			NULL,  /* pointer to process security attributes  */
484 			NULL,  /* pointer to thread security attributes   */
485 			TRUE,  /* handle inheritance flag                 */
486 			GetPriorityClass(GetCurrentProcess()),
487 			/* creation flags                          */
488 			NULL,  /* pointer to new environment block        */
489 			BinDir,/* pointer to current directory name       */
490 			&si,	  /* pointer to STARTUPINFO                  */
491 			&ErlProcessInfo /* pointer to PROCESS_INFORMATION */
492 			)) == FALSE) {
493 	ShowLastError();
494 	exit_help("Failed to start new node");
495     }
496 
497 #ifdef _DEBUG
498     fprintf(stderr, "Waiting for Erlang to terminate.\n");
499 #endif
500     if(MsgWaitForMultipleObjects(1,&ErlProcessInfo.hProcess, FALSE,
501 				 INFINITE, QS_POSTMESSAGE) == WAIT_OBJECT_0+1){
502 	if(PostThreadMessage(ErlProcessInfo.dwThreadId,
503 			     WM_USER,
504 			     (WPARAM) 0,
505 			     (LPARAM) 0)){
506 	    /* Wait 10 seconds for erl process to die, elsee terminate it. */
507 	    if(WaitForSingleObject(ErlProcessInfo.hProcess, 10000)
508 	       != WAIT_OBJECT_0){
509 		TerminateProcess(ErlProcessInfo.hProcess,0);
510 	    }
511 	} else {
512 	   TerminateProcess(ErlProcessInfo.hProcess,0);
513 	}
514     }
515     GetExitCodeProcess(ErlProcessInfo.hProcess, &dwExitCode);
516 #ifdef _DEBUG
517     fprintf(stderr, "Erlang terminated.\n");
518 #endif
519 
520     free(CommandLine);
521     return(dwExitCode);
522 }
523 
524 
525 /*
526  * Try to make the needed options complete by looking through the data file,
527  * environment variables and registry entries.
528  */
complete_options(void)529 void complete_options(void)
530 {
531     int len;
532     /* Try to find a descent RelDir */
533     if( !RelDir ) {
534 	DWORD sz = 32;
535 	while (1) {
536 	    DWORD nsz;
537 	    if (RelDir)
538 		free(RelDir);
539 	    RelDir = malloc(sz*sizeof(wchar_t));
540 	    if (!RelDir) {
541 		fprintf(stderr, "** Error : failed to allocate memory\n");
542 		exit(1);
543 	    }
544 	    SetLastError(0);
545 	    nsz = GetEnvironmentVariableW(L"RELDIR", RelDir, sz);
546 	    if (nsz == 0 && GetLastError() == ERROR_ENVVAR_NOT_FOUND) {
547 		free(RelDir);
548 		RelDir = NULL;
549 		break;
550 	    }
551 	    else if (nsz <= sz)
552 		break;
553 	    else
554 		sz = nsz;
555 	}
556 	if (RelDir == NULL) {
557 	  if (!RootDir) {
558 	    /* Impossible to find all data... */
559 	    exit_help("Need either Root directory nor Release directory.");
560 	  }
561 	  /* Ok, construct our own RelDir from RootDir */
562           sz = wcslen(RootDir)+wcslen(RELEASE_SUBDIR)+1;
563 	  RelDir = (wchar_t *) malloc(sz * sizeof(wchar_t));
564 	  assert(RelDir);
565 	  swprintf(RelDir, sz, L"%s" RELEASE_SUBDIR, RootDir);
566 	  read_datafile();
567 	} else {
568 	    read_datafile();
569 	}
570     } else {
571 	read_datafile();
572     }
573     if( !RootDir ) {
574 	/* Try to construct RootDir from RelDir */
575 	wchar_t *p;
576 	RootDir = malloc((wcslen(RelDir)+1)*sizeof(wchar_t));
577 	wcscpy(RootDir,RelDir);
578 	p = RootDir+wcslen(RootDir)-1;
579 	if (p >= RootDir && (*p == L'/' || *p == L'\\'))
580 	    --p;
581 	while (p >= RootDir && *p != L'/' &&  *p != L'\\')
582 	    --p;
583 	if (p <= RootDir) { /* Empty RootDir is also an error */
584 	    exit_help("Cannot determine Root directory from "
585 		      "Release directory.");
586 	}
587 	*p = L'\0';
588     }
589 
590     len = wcslen(RootDir)+wcslen(ERTS_SUBDIR_PREFIX)+
591 	wcslen(Version)+wcslen(BIN_SUBDIR)+1;
592     BinDir = (wchar_t *) malloc(len * sizeof(wchar_t));
593     assert(BinDir);
594     swprintf(BinDir, len, L"%s" ERTS_SUBDIR_PREFIX L"%s" BIN_SUBDIR, RootDir, Version);
595 
596     read_bootflags();
597 
598 #ifdef _DEBUG
599     fprintf(stderr, "RelDir: '%S'\n", RelDir);
600     fprintf(stderr, "BinDir: '%S'\n", BinDir);
601 #endif
602 }
603 
604 
605 
606 
LogoffHandlerRoutine(DWORD dwCtrlType)607 BOOL WINAPI LogoffHandlerRoutine( DWORD dwCtrlType )
608 {
609     if(dwCtrlType == CTRL_LOGOFF_EVENT) {
610 	return TRUE;
611     }
612     if(dwCtrlType == CTRL_SHUTDOWN_EVENT) {
613 	return TRUE;
614     }
615 
616     return FALSE;
617 }
618 
619 
620 
621 
main(void)622 int main(void)
623 {
624     DWORD	dwExitCode;
625     wchar_t	*cmdline;
626 
627     /* Make sure a logoff does not distrurb us. */
628     SetConsoleCtrlHandler(LogoffHandlerRoutine, TRUE);
629 
630     cmdline = GetCommandLineW();
631     assert(cmdline);
632 
633     CommandLineStart = (wchar_t *) malloc((wcslen(cmdline) + 1)*sizeof(wchar_t));
634     assert(CommandLineStart);
635     wcscpy(CommandLineStart,cmdline);
636 
637     split_commandline();
638     parse_commandline();
639     complete_options();
640 
641     /* We now have all the options we need in order to fire up a new node.. */
642     dwExitCode = start_new_node();
643 
644     return( (int) dwExitCode );
645 }
646 
647 
648