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