1 /*
2 * %CopyrightBegin%
3 *
4 * Copyright Ericsson AB 2007-2018. 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 * Purpose: escript front-end.
22 */
23
24 #include "etc_common.h"
25
26 static int debug = 0; /* Bit flags for debug printouts. */
27
28 static char** eargv_base; /* Base of vector. */
29 static char** eargv; /* First argument for erl. */
30
31 static int eargc; /* Number of arguments in eargv. */
32
33 #define BOOL int
34 #define FALSE 0
35 #define TRUE 1
36
37 #ifdef __WIN32__
38 # define QUOTE(s) possibly_quote(s)
39 # define IS_DIRSEP(c) ((c) == '/' || (c) == '\\')
40 # define DIRSEPSTR "\\"
41 # define LDIRSEPSTR L"\\"
42 # define LPATHSEPSTR L";"
43 # define PMAX MAX_PATH
44 # define ERL_NAME "erl.exe"
45 #else
46 # define QUOTE(s) s
47 # define IS_DIRSEP(c) ((c) == '/')
48 # define DIRSEPSTR "/"
49 # define PATHSEPSTR ":"
50 # define PMAX PATH_MAX
51 # define ERL_NAME "erl"
52 #endif
53
54 #define UNSHIFT(s) eargc++, eargv--; eargv[0] = QUOTE(s)
55 #define UNSHIFT3(s, t, u) UNSHIFT(u); UNSHIFT(t); UNSHIFT(s)
56 #define PUSH(s) eargv[eargc++] = QUOTE(s)
57 #define PUSH2(s, t) PUSH(s); PUSH(t)
58 #define PUSH3(s, t, u) PUSH2(s, t); PUSH(u)
59 #define LINEBUFSZ 1024
60
61 /*
62 * Local functions.
63 */
64
65 static void error(char* format, ...);
66 static void* emalloc(size_t size);
67 static void efree(void *p);
68 static char* strsave(char* string);
69 static int run_erlang(char* name, char** argv);
70 static char* get_default_emulator(char* progname);
71 #ifdef __WIN32__
72 static char* possibly_quote(char* arg);
73 static void* erealloc(void *p, size_t size);
74 #endif
75
76 /*
77 * Supply a strerror() function if libc doesn't.
78 */
79 #ifndef HAVE_STRERROR
80
81 extern int sys_nerr;
82
83 #ifndef SYS_ERRLIST_DECLARED
84 extern const char * const sys_errlist[];
85 #endif /* !SYS_ERRLIST_DECLARED */
86
strerror(int errnum)87 char *strerror(int errnum)
88 {
89 static char *emsg[1024];
90
91 if (errnum != 0) {
92 if (errnum > 0 && errnum < sys_nerr)
93 sprintf((char *) &emsg[0], "(%s)", sys_errlist[errnum]);
94 else
95 sprintf((char *) &emsg[0], "errnum = %d ", errnum);
96 }
97 else {
98 emsg[0] = '\0';
99 }
100 return (char *) &emsg[0];
101 }
102 #endif /* !HAVE_STRERROR */
103
104 static char *
get_env(char * key)105 get_env(char *key)
106 {
107 #ifdef __WIN32__
108 DWORD size = 32;
109 char *value=NULL;
110 wchar_t *wcvalue = NULL;
111 wchar_t wckey[256];
112 int len;
113
114 MultiByteToWideChar(CP_UTF8, 0, key, -1, wckey, 256);
115
116 while (1) {
117 DWORD nsz;
118 if (wcvalue)
119 efree(wcvalue);
120 wcvalue = (wchar_t *) emalloc(size*sizeof(wchar_t));
121 SetLastError(0);
122 nsz = GetEnvironmentVariableW(wckey, wcvalue, size);
123 if (nsz == 0 && GetLastError() == ERROR_ENVVAR_NOT_FOUND) {
124 efree(wcvalue);
125 return NULL;
126 }
127 if (nsz <= size) {
128 len = WideCharToMultiByte(CP_UTF8, 0, wcvalue, -1, NULL, 0, NULL, NULL);
129 value = emalloc(len*sizeof(char));
130 WideCharToMultiByte(CP_UTF8, 0, wcvalue, -1, value, len, NULL, NULL);
131 efree(wcvalue);
132 return value;
133 }
134 size = nsz;
135 }
136 #else
137 return getenv(key);
138 #endif
139 }
140
141 static void
set_env(char * key,char * value)142 set_env(char *key, char *value)
143 {
144 #ifdef __WIN32__
145 WCHAR wkey[MAXPATHLEN];
146 WCHAR wvalue[MAXPATHLEN];
147 MultiByteToWideChar(CP_UTF8, 0, key, -1, wkey, MAXPATHLEN);
148 MultiByteToWideChar(CP_UTF8, 0, value, -1, wvalue, MAXPATHLEN);
149 if (!SetEnvironmentVariableW(wkey, wvalue))
150 error("SetEnvironmentVariable(\"%s\", \"%s\") failed!", key, value);
151 #else
152 size_t size = strlen(key) + 1 + strlen(value) + 1;
153 char *str = emalloc(size);
154 sprintf(str, "%s=%s", key, value);
155 if (putenv(str) != 0)
156 error("putenv(\"%s\") failed!", str);
157 #ifdef HAVE_COPYING_PUTENV
158 efree(str);
159 #endif
160 #endif
161 }
162
163 /*
164 * Find absolute path to this program
165 */
166
167 #ifdef __WIN32__
168 static char *
find_prog(char * origpath)169 find_prog(char *origpath)
170 {
171 wchar_t relpath[PMAX];
172 wchar_t abspath[PMAX];
173
174 if (strlen(origpath) >= PMAX)
175 error("Path too long");
176
177 MultiByteToWideChar(CP_UTF8, 0, origpath, -1, relpath, PMAX);
178
179 if (wcsstr(relpath, LDIRSEPSTR) == NULL) {
180 /* Just a base name */
181 int sz;
182 wchar_t *envpath;
183 sz = GetEnvironmentVariableW(L"PATH", NULL, 0);
184 if (sz) {
185 /* Try to find the executable in the path */
186 wchar_t dir[PMAX];
187 wchar_t *beg;
188 wchar_t *end;
189
190 HANDLE dir_handle; /* Handle to directory. */
191 wchar_t wildcard[PMAX]; /* Wildcard to search for. */
192 WIN32_FIND_DATAW find_data; /* Data found by FindFirstFile() or FindNext(). */
193
194 BOOL look_for_sep = TRUE;
195
196 envpath = (wchar_t *) emalloc(sz * sizeof(wchar_t*));
197 GetEnvironmentVariableW(L"PATH", envpath, sz);
198 beg = envpath;
199
200 while (look_for_sep) {
201 end = wcsstr(beg, LPATHSEPSTR);
202 if (end != NULL) {
203 sz = end - beg;
204 } else {
205 sz = wcslen(beg);
206 look_for_sep = FALSE;
207 }
208 if (sz >= PMAX) {
209 beg = end + 1;
210 continue;
211 }
212 wcsncpy(dir, beg, sz);
213 dir[sz] = L'\0';
214 beg = end + 1;
215
216 swprintf(wildcard, PMAX, L"%s" LDIRSEPSTR L"%s",
217 dir, relpath /* basename */);
218 dir_handle = FindFirstFileW(wildcard, &find_data);
219 if (dir_handle == INVALID_HANDLE_VALUE) {
220 /* Try next directory in path */
221 continue;
222 } else {
223 /* Wow we found the executable. */
224 wcscpy(relpath, wildcard);
225 FindClose(dir_handle);
226 look_for_sep = FALSE;
227 break;
228 }
229 }
230 efree(envpath);
231 }
232 }
233
234 {
235 DWORD size;
236 wchar_t *absrest;
237 size = GetFullPathNameW(relpath, PMAX, abspath, &absrest);
238 if ((size == 0) || (size > PMAX)) {
239 /* Cannot determine absolute path to escript. Try the origin. */
240 return strsave(origpath);
241 } else {
242 char utf8abs[PMAX];
243 WideCharToMultiByte(CP_UTF8, 0, abspath, -1, utf8abs, PMAX, NULL, NULL);
244 return strsave(utf8abs);
245 }
246 }
247 }
248 #else
249 static char *
find_prog(char * origpath)250 find_prog(char *origpath)
251 {
252 char relpath[PMAX];
253 char abspath[PMAX];
254
255 if (strlen(origpath) >= sizeof(relpath))
256 error("Path too long");
257
258 strcpy(relpath, origpath);
259
260 if (strstr(relpath, DIRSEPSTR) == NULL) {
261 /* Just a base name */
262 char *envpath;
263
264 envpath = get_env("PATH");
265 if (envpath) {
266 /* Try to find the executable in the path */
267 char dir[PMAX];
268 char *beg = envpath;
269 char *end;
270 int sz;
271
272 DIR *dp; /* Pointer to directory structure. */
273 struct dirent* dirp; /* Pointer to directory entry. */
274
275 BOOL look_for_sep = TRUE;
276
277 while (look_for_sep) {
278 end = strstr(beg, PATHSEPSTR);
279 if (end != NULL) {
280 sz = end - beg;
281 } else {
282 sz = strlen(beg);
283 look_for_sep = FALSE;
284 }
285 if (sz >= sizeof(dir)) {
286 beg = end + 1;
287 continue;
288 }
289 strncpy(dir, beg, sz);
290 dir[sz] = '\0';
291 beg = end + 1;
292
293 dp = opendir(dir);
294 if (dp != NULL) {
295 while (TRUE) {
296 dirp = readdir(dp);
297 if (dirp == NULL) {
298 closedir(dp);
299 /* Try next directory in path */
300 break;
301 }
302
303 if (strcmp(origpath, dirp->d_name) == 0) {
304 /* Wow we found the executable. */
305 erts_snprintf(relpath, sizeof(relpath), "%s" DIRSEPSTR "%s",
306 dir, dirp->d_name);
307 closedir(dp);
308 look_for_sep = FALSE;
309 break;
310 }
311 }
312 }
313 }
314 }
315 }
316
317 {
318 if (!realpath(relpath, abspath)) {
319 /* Cannot determine absolute path to escript. Try the origin. */
320 return strsave(origpath);
321 } else {
322 return strsave(abspath);
323 }
324 }
325 }
326 #endif
327
328 static void
append_shebang_args(char * scriptname)329 append_shebang_args(char* scriptname)
330 {
331 /* Open script file */
332 FILE* fd;
333 #ifdef __WIN32__
334 wchar_t wcscriptname[PMAX];
335
336 MultiByteToWideChar(CP_UTF8, 0, scriptname, -1, wcscriptname, PMAX);
337 fd = _wfopen(wcscriptname, L"r");
338 #else
339 fd = fopen (scriptname,"r");
340 #endif
341
342 if (fd != NULL) {
343 /* Read first line in script file */
344 static char linebuf[LINEBUFSZ];
345 char* ptr = fgets(linebuf, LINEBUFSZ, fd);
346
347 if (ptr != NULL) {
348 /* Try to find args on second or third line */
349 ptr = fgets(linebuf, LINEBUFSZ, fd);
350 if (ptr != NULL && linebuf[0] == '%' && linebuf[1] == '%' && linebuf[2] == '!') {
351 /* Use second line */
352 } else {
353 /* Try third line */
354 ptr = fgets(linebuf, LINEBUFSZ, fd);
355 if (ptr != NULL && linebuf[0] == '%' && linebuf[1] == '%' && linebuf[2] == '!') {
356 /* Use third line */
357 } else {
358 /* Do not use any line */
359 ptr = NULL;
360 }
361 }
362
363 if (ptr != NULL) {
364 /* Use entire line but the leading chars */
365 char* beg = linebuf + 3;
366 char* end;
367 BOOL newline = FALSE;
368
369 /* Push all args */
370 while(beg && !newline) {
371 /* Skip leading spaces */
372 while (beg && beg[0] == ' ') {
373 beg++;
374 }
375
376 /* Find end of arg */
377 end = beg;
378 while (end && end < (linebuf+LINEBUFSZ-1) && end[0] != ' ') {
379 if (end[0] == '\n') {
380 newline = TRUE;
381 end[0]= '\0';
382 break;
383 } else {
384 end++;
385 }
386 }
387
388 /* Empty arg */
389 if (beg == end) {
390 break;
391 }
392 end[0]= '\0';
393 PUSH(beg);
394 beg = end + 1;
395 }
396 }
397 }
398 fclose(fd);
399 } else {
400 error("Failed to open file: %s", scriptname);
401 }
402 }
403
404 #ifdef __WIN32__
wmain(int argc,wchar_t ** wcargv)405 int wmain(int argc, wchar_t **wcargv)
406 {
407 char** argv;
408 #else
409 int
410 main(int argc, char** argv)
411 {
412 #endif
413 int eargv_size;
414 int eargc_base; /* How many arguments in the base of eargv. */
415 char* emulator;
416 char* basename;
417 char* def_emu_lookup_path;
418 char scriptname[PMAX];
419 char** last_opt;
420 char** first_opt;
421
422 #ifdef __WIN32__
423 int i;
424 int len;
425 /* Convert argv to utf8 */
426 argv = emalloc((argc+1) * sizeof(char*));
427 for (i=0; i<argc; i++) {
428 len = WideCharToMultiByte(CP_UTF8, 0, wcargv[i], -1, NULL, 0, NULL, NULL);
429 argv[i] = emalloc(len*sizeof(char));
430 WideCharToMultiByte(CP_UTF8, 0, wcargv[i], -1, argv[i], len, NULL, NULL);
431 }
432 argv[argc] = NULL;
433 #endif
434
435 /*
436 * Allocate the argv vector to be used for arguments to Erlang.
437 * Arrange for starting to pushing information in the middle of
438 * the array, to allow easy addition of commands in the beginning.
439 */
440
441 eargv_size = argc*4+1000+LINEBUFSZ/2;
442 eargv_base = (char **) emalloc(eargv_size*sizeof(char*));
443 eargv = eargv_base;
444 eargc = 0;
445 eargc_base = eargc;
446 eargv = eargv + eargv_size/2;
447 eargc = 0;
448
449 /* Determine basename of the executable */
450 for (basename = argv[0]+strlen(argv[0]);
451 basename > argv[0] && !(IS_DIRSEP(basename[-1]));
452 --basename)
453 ;
454
455 first_opt = argv;
456 last_opt = argv;
457
458 #ifdef __WIN32__
459 if ( (_stricmp(basename, "escript.exe") == 0)
460 ||(_stricmp(basename, "escript") == 0)) {
461 #else
462 if (strcmp(basename, "escript") == 0) {
463 #endif
464 def_emu_lookup_path = argv[0];
465 /*
466 * Locate all options before the script name.
467 */
468
469 while (argc > 1 && argv[1][0] == '-') {
470 argc--;
471 argv++;
472 last_opt = argv;
473 }
474
475 if (argc <= 1) {
476 error("Missing filename\n");
477 }
478 strncpy(scriptname, argv[1], sizeof(scriptname));
479 scriptname[sizeof(scriptname)-1] = '\0';
480 argc--;
481 argv++;
482 } else {
483 char *absname = find_prog(argv[0]);
484 #ifdef __WIN32__
485 int len = strlen(absname);
486 if (len >= 4 && _stricmp(absname+len-4, ".exe") == 0) {
487 absname[len-4] = '\0';
488 }
489 #endif
490 erts_snprintf(scriptname, sizeof(scriptname), "%s.escript",
491 absname);
492 efree(absname);
493 def_emu_lookup_path = scriptname;
494 }
495
496 /* Determine path to emulator */
497 emulator = get_env("ESCRIPT_EMULATOR");
498
499 if (emulator == NULL) {
500 emulator = get_default_emulator(def_emu_lookup_path);
501 }
502
503 if (strlen(emulator) >= PMAX)
504 error("Value of environment variable ESCRIPT_EMULATOR is too large");
505
506 /*
507 * Push initial arguments.
508 */
509
510 PUSH(emulator);
511
512 PUSH("+B");
513 PUSH2("-boot", "no_dot_erlang");
514 PUSH("-noshell");
515
516 /*
517 * Read options from the %%! row in the script and add them as args
518 */
519
520 append_shebang_args(scriptname);
521
522 /*
523 * Push the script name and everything following it as extra arguments.
524 */
525
526 PUSH3("-run", "escript", "start");
527
528 /*
529 * Push all options before the script name. But omit the leading hyphens.
530 */
531
532 while (first_opt != last_opt) {
533 PUSH(first_opt[1]+1);
534 first_opt++;
535 }
536
537 PUSH("-extra");
538 PUSH(scriptname);
539 while (argc > 1) {
540 PUSH(argv[1]);
541 argc--, argv++;
542 }
543
544 /*
545 * Move up the commands for invoking the emulator and adjust eargv
546 * accordingly.
547 */
548
549 while (--eargc_base >= 0) {
550 UNSHIFT(eargv_base[eargc_base]);
551 }
552
553 /*
554 * Add scriptname to env
555 */
556 set_env("ESCRIPT_NAME", scriptname);
557
558 /*
559 * Invoke Erlang with the collected options.
560 */
561
562 PUSH(NULL);
563 return run_erlang(eargv[0], eargv);
564 }
565
566 #ifdef __WIN32__
567 wchar_t *make_commandline(char **argv)
568 {
569 static wchar_t *buff = NULL;
570 static int siz = 0;
571 int num = 0, len;
572 char **arg;
573 wchar_t *p;
574
575 if (*argv == NULL) {
576 return L"";
577 }
578 for (arg = argv; *arg != NULL; ++arg) {
579 num += strlen(*arg)+1;
580 }
581 if (!siz) {
582 siz = num;
583 buff = (wchar_t *) emalloc(siz*sizeof(wchar_t));
584 } else if (siz < num) {
585 siz = num;
586 buff = (wchar_t *) erealloc(buff,siz*sizeof(wchar_t));
587 }
588 p = buff;
589 num=0;
590 for (arg = argv; *arg != NULL; ++arg) {
591 len = MultiByteToWideChar(CP_UTF8, 0, *arg, -1, p, siz);
592 p+=(len-1);
593 *p++=L' ';
594 }
595 *(--p) = L'\0';
596
597 if (debug) {
598 printf("Processed command line:%S\n",buff);
599 }
600 return buff;
601 }
602
603 int my_spawnvp(char **argv)
604 {
605 STARTUPINFOW siStartInfo;
606 PROCESS_INFORMATION piProcInfo;
607 DWORD ec;
608
609 memset(&siStartInfo,0,sizeof(STARTUPINFOW));
610 siStartInfo.cb = sizeof(STARTUPINFOW);
611 siStartInfo.dwFlags = STARTF_USESTDHANDLES;
612 siStartInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
613 siStartInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
614 siStartInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
615
616 if (!CreateProcessW(NULL,
617 make_commandline(argv),
618 NULL,
619 NULL,
620 TRUE,
621 0,
622 NULL,
623 NULL,
624 &siStartInfo,
625 &piProcInfo)) {
626 return -1;
627 }
628 CloseHandle(piProcInfo.hThread);
629
630 WaitForSingleObject(piProcInfo.hProcess,INFINITE);
631 if (!GetExitCodeProcess(piProcInfo.hProcess,&ec)) {
632 return 0;
633 }
634 return (int) ec;
635 }
636 #endif /* __WIN32__ */
637
638
639 static int
640 run_erlang(char* progname, char** argv)
641 {
642 #ifdef __WIN32__
643 int status;
644 #endif
645
646 if (debug) {
647 int i = 0;
648 while (argv[i] != NULL)
649 printf(" %s", argv[i++]);
650 printf("\n");
651 }
652
653 #ifdef __WIN32__
654 /*
655 * Alas, we must wait here for the program to finish.
656 * Otherwise, the shell from which we was executed will think
657 * we are finished and print a prompt and read keyboard input.
658 */
659
660 status = my_spawnvp(argv)/*_spawnvp(_P_WAIT,progname,argv)*/;
661 if (status == -1) {
662 fprintf(stderr, "escript: Error executing '%s': %d", progname,
663 GetLastError());
664 }
665 return status;
666 #else
667 execvp(progname, argv);
668 error("Error %d executing \'%s\'.", errno, progname);
669 return 2;
670 #endif
671 }
672
673 static void
674 error(char* format, ...)
675 {
676 char sbuf[1024];
677 va_list ap;
678
679 va_start(ap, format);
680 erts_vsnprintf(sbuf, sizeof(sbuf), format, ap);
681 va_end(ap);
682 fprintf(stderr, "escript: %s\n", sbuf);
683 exit(1);
684 }
685
686 static void*
687 emalloc(size_t size)
688 {
689 void *p = malloc(size);
690 if (p == NULL)
691 error("Insufficient memory");
692 return p;
693 }
694
695 #ifdef __WIN32__
696 static void *
697 erealloc(void *p, size_t size)
698 {
699 void *res = realloc(p, size);
700 if (res == NULL)
701 error("Insufficient memory");
702 return res;
703 }
704 #endif
705
706 static void
707 efree(void *p)
708 {
709 free(p);
710 }
711
712 static char*
713 strsave(char* string)
714 {
715 char* p = emalloc(strlen(string)+1);
716 strcpy(p, string);
717 return p;
718 }
719
720 static int
721 file_exists(char *progname)
722 {
723 #ifdef __WIN32__
724 wchar_t wcsbuf[MAXPATHLEN];
725 MultiByteToWideChar(CP_UTF8, 0, progname, -1, wcsbuf, MAXPATHLEN);
726 return (_waccess(wcsbuf, 0) != -1);
727 #else
728 return (access(progname, 1) != -1);
729 #endif
730 }
731
732 static char*
733 get_default_emulator(char* progname)
734 {
735 char sbuf[MAXPATHLEN];
736 char* s;
737
738 if (strlen(progname) >= sizeof(sbuf))
739 return ERL_NAME;
740
741 strcpy(sbuf, progname);
742 for (s = sbuf+strlen(sbuf); s >= sbuf; s--) {
743 if (IS_DIRSEP(*s)) {
744 strcpy(s+1, ERL_NAME);
745 if(file_exists(sbuf))
746 return strsave(sbuf);
747 strcpy(s+1, "bin" DIRSEPSTR ERL_NAME);
748 if(file_exists(sbuf))
749 return strsave(sbuf);
750 break;
751 }
752 }
753 return ERL_NAME;
754 }
755
756 #ifdef __WIN32__
757 static char*
758 possibly_quote(char* arg)
759 {
760 int mustQuote = FALSE;
761 int n = 0;
762 char* s;
763 char* narg;
764
765 if (arg == NULL) {
766 return arg;
767 }
768
769 /*
770 * Scan the string to find out if it needs quoting and return
771 * the original argument if not.
772 */
773
774 for (s = arg; *s; s++, n++) {
775 switch(*s) {
776 case ' ':
777 mustQuote = TRUE;
778 continue;
779 case '"':
780 mustQuote = TRUE;
781 n++;
782 continue;
783 case '\\':
784 if(s[1] == '"')
785 n++;
786 continue;
787 default:
788 continue;
789 }
790 }
791 if (!mustQuote) {
792 return arg;
793 }
794
795 /*
796 * Insert the quotes and put a backslash in front of every quote
797 * inside the string.
798 */
799
800 s = narg = emalloc(n+2+1);
801 for (*s++ = '"'; *arg; arg++, s++) {
802 if (*arg == '"' || (*arg == '\\' && arg[1] == '"')) {
803 *s++ = '\\';
804 }
805 *s = *arg;
806 }
807 if (s[-1] == '\\') {
808 *s++ ='\\';
809 }
810 *s++ = '"';
811 *s = '\0';
812 return narg;
813 }
814 #endif /* __WIN32__ */
815