diff options
Diffstat (limited to 'erts/etc/common/escript.c')
-rw-r--r-- | erts/etc/common/escript.c | 697 |
1 files changed, 697 insertions, 0 deletions
diff --git a/erts/etc/common/escript.c b/erts/etc/common/escript.c new file mode 100644 index 0000000000..ab37d4af46 --- /dev/null +++ b/erts/etc/common/escript.c @@ -0,0 +1,697 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2007-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% + */ +/* + * Purpose: escript front-end. + */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "sys.h" +#ifdef __WIN32__ +#include <winbase.h> +#endif + +#include <ctype.h> + +static int debug = 0; /* Bit flags for debug printouts. */ + +static char** eargv_base; /* Base of vector. */ +static char** eargv; /* First argument for erl. */ + +static int eargc; /* Number of arguments in eargv. */ + +#define BOOL int +#define FALSE 0 +#define TRUE 1 + +#ifdef __WIN32__ +# define QUOTE(s) possibly_quote(s) +# define IS_DIRSEP(c) ((c) == '/' || (c) == '\\') +# define DIRSEPSTR "\\" +# define PATHSEPSTR ";" +# define PMAX MAX_PATH +# define ERL_NAME "erl.exe" +#else +# define QUOTE(s) s +# define IS_DIRSEP(c) ((c) == '/') +# define DIRSEPSTR "/" +# define PATHSEPSTR ":" +# define PMAX PATH_MAX +# define ERL_NAME "erl" +#endif + +#define UNSHIFT(s) eargc++, eargv--; eargv[0] = QUOTE(s) +#define UNSHIFT3(s, t, u) UNSHIFT(u); UNSHIFT(t); UNSHIFT(s) +#define PUSH(s) eargv[eargc++] = QUOTE(s) +#define PUSH2(s, t) PUSH(s); PUSH(t) +#define PUSH3(s, t, u) PUSH2(s, t); PUSH(u) +#define LINEBUFSZ 1024 + +/* + * Local functions. + */ + +static void error(char* format, ...); +static char* emalloc(size_t size); +static void efree(void *p); +static char* strsave(char* string); +static void push_words(char* src); +static int run_erlang(char* name, char** argv); +static char* get_default_emulator(char* progname); +#ifdef __WIN32__ +static char* possibly_quote(char* arg); +#endif + +/* + * Supply a strerror() function if libc doesn't. + */ +#ifndef HAVE_STRERROR + +extern int sys_nerr; + +#ifndef SYS_ERRLIST_DECLARED +extern const char * const sys_errlist[]; +#endif /* !SYS_ERRLIST_DECLARED */ + +char *strerror(int errnum) +{ + static char *emsg[1024]; + + if (errnum != 0) { + if (errnum > 0 && errnum < sys_nerr) + sprintf((char *) &emsg[0], "(%s)", sys_errlist[errnum]); + else + sprintf((char *) &emsg[0], "errnum = %d ", errnum); + } + else { + emsg[0] = '\0'; + } + return (char *) &emsg[0]; +} +#endif /* !HAVE_STRERROR */ + +static char * +get_env(char *key) +{ +#ifdef __WIN32__ + DWORD size = 32; + char *value = NULL; + while (1) { + DWORD nsz; + if (value) + efree(value); + value = emalloc(size); + SetLastError(0); + nsz = GetEnvironmentVariable((LPCTSTR) key, (LPTSTR) value, size); + if (nsz == 0 && GetLastError() == ERROR_ENVVAR_NOT_FOUND) { + efree(value); + return NULL; + } + if (nsz <= size) + return value; + size = nsz; + } +#else + return getenv(key); +#endif +} + +static void +free_env_val(char *value) +{ +#ifdef __WIN32__ + if (value) + efree(value); +#endif +} +/* + * Find absolute path to this program + */ + +static char * +find_prog(char *origpath) +{ + char relpath[PMAX]; + char abspath[PMAX]; + + strcpy(relpath, origpath); + + if (strstr(relpath, DIRSEPSTR) == NULL) { + /* Just a base name */ + char *envpath; + + envpath = get_env("PATH"); + if (envpath) { + /* Try to find the executable in the path */ + char dir[PMAX]; + char *beg = envpath; + char *end; + int sz; + +#ifdef __WIN32__ + HANDLE dir_handle; /* Handle to directory. */ + char wildcard[PMAX]; /* Wildcard to search for. */ + WIN32_FIND_DATA find_data; /* Data found by FindFirstFile() or FindNext(). */ +#else + DIR *dp; /* Pointer to directory structure. */ + struct dirent* dirp; /* Pointer to directory entry. */ +#endif /* __WIN32__ */ + + BOOL look_for_sep = TRUE; + + while (look_for_sep) { + end = strstr(beg, PATHSEPSTR); + if (end != NULL) { + sz = end - beg; + strncpy(dir, beg, sz); + dir[sz] = '\0'; + } else { + sz = strlen(beg); + strcpy(dir, beg); + look_for_sep = FALSE; + } + beg = end + 1; + +#ifdef __WIN32__ + strcpy(wildcard, dir); + strcat(wildcard, DIRSEPSTR); + strcat(wildcard, relpath); /* basename */ + dir_handle = FindFirstFile(wildcard, &find_data); + if (dir_handle == INVALID_HANDLE_VALUE) { + /* Try next directory in path */ + continue; + } else { + /* Wow we found the executable. */ + strcpy(abspath, wildcard); + FindClose(dir_handle); + return strsave(abspath); + } +#else + dp = opendir(dir); + if (dp != NULL) { + while (TRUE) { + dirp = readdir(dp); + if (dirp == NULL) { + closedir(dp); + /* Try next directory in path */ + break; + } + + if (strcmp(origpath, dirp->d_name) == 0) { + /* Wow we found the executable. */ + strcpy(abspath, dir); + strcat(abspath, DIRSEPSTR); + strcat(abspath, dirp->d_name); + closedir(dp); + return strsave(abspath); + } + } + } +#endif /* __WIN32__ */ + } + } + } + + { +#ifdef __WIN32__ + DWORD size; + char *absrest; + size = GetFullPathName(relpath, PMAX, abspath, &absrest); + if ((size == 0) || (size > PMAX)) { + +#else + if (!realpath(relpath, abspath)) { +#endif /* __WIN32__ */ + /* Cannot determine absolute path to escript. Try the relative. */ + return strsave(relpath); + } else { + return strsave(abspath); + } + } +} + +static void +append_shebang_args(char* scriptname) +{ + /* Open script file */ + FILE* fd = fopen (scriptname,"r"); + + if (fd != NULL) { + /* Read first line in script file */ + static char linebuf[LINEBUFSZ]; + char* ptr = fgets(linebuf, LINEBUFSZ, fd); + + if (ptr != NULL && linebuf[0] == '#' && linebuf[1] == '!') { + /* Try to find args on second or third line */ + ptr = fgets(linebuf, LINEBUFSZ, fd); + if (ptr != NULL && linebuf[0] == '%' && linebuf[1] == '%' && linebuf[2] == '!') { + /* Use second line */ + } else { + /* Try third line */ + ptr = fgets(linebuf, LINEBUFSZ, fd); + if (ptr != NULL && linebuf[0] == '%' && linebuf[1] == '%' && linebuf[2] == '!') { + /* Use third line */ + } else { + /* Do not use any line */ + ptr = NULL; + } + } + + if (ptr != NULL) { + /* Use entire line but the leading chars */ + char* beg = linebuf + 3; + char* end; + BOOL newline = FALSE; + + /* Push all args */ + while(beg && !newline) { + /* Skip leading spaces */ + while (beg && beg[0] == ' ') { + beg++; + } + + /* Find end of arg */ + end = beg; + while (end && end[0] != ' ') { + if (end[0] == '\n') { + newline = TRUE; + end[0]= '\0'; + break; + } else { + end++; + } + } + + /* Empty arg */ + if (beg == end) { + break; + } + end[0]= '\0'; + PUSH(beg); + beg = end + 1; + } + } + } + fclose(fd); + } else { + error("Failed to open file: %s", scriptname); + } +} + +int +main(int argc, char** argv) +{ + int eargv_size; + int eargc_base; /* How many arguments in the base of eargv. */ + char* emulator; + char* env; + char* basename; + char* absname; + char scriptname[PMAX]; + char** last_opt; + char** first_opt; + + emulator = env = get_env("ESCRIPT_EMULATOR"); + if (emulator == NULL) { + emulator = get_default_emulator(argv[0]); + } + + /* + * Allocate the argv vector to be used for arguments to Erlang. + * Arrange for starting to pushing information in the middle of + * the array, to allow easy addition of commands in the beginning. + */ + + eargv_size = argc*4+1000; + eargv_base = (char **) emalloc(eargv_size*sizeof(char*)); + eargv = eargv_base; + eargc = 0; + push_words(emulator); + eargc_base = eargc; + eargv = eargv + eargv_size/2; + eargc = 0; + + free_env_val(env); + + /* + * Push initial arguments. + */ + + PUSH("+B"); + PUSH2("-boot", "start_clean"); + PUSH("-noshell"); + + /* Determine basename of the executable */ + for (basename = argv[0]+strlen(argv[0]); + basename > argv[0] && !(IS_DIRSEP(basename[-1])); + --basename) + ; + + first_opt = argv; + last_opt = argv; + +#ifdef __WIN32__ + if (_stricmp(basename, "escript.exe") == 0) { +#else + if (strcmp(basename, "escript") == 0) { +#endif + /* + * Push all options (without the hyphen) before the script name. + */ + + while (argc > 1 && argv[1][0] == '-') { + PUSH(argv[1]+1); + argc--; + argv++; + last_opt = argv; + } + + if (argc <= 1) { + error("Missing filename\n"); + } + strcpy(scriptname, argv[1]); + argc--; + argv++; + } else { +#ifdef __WIN32__ + int len; +#endif + absname = find_prog(argv[0]); + strcpy(scriptname, absname); + efree(absname); +#ifdef __WIN32__ + len = strlen(scriptname); + if (len >= 4 && _stricmp(scriptname+len-4, ".exe") == 0) { + scriptname[len-4] = '\0'; + } +#endif + strcat(scriptname, ".escript"); + } + + /* + * Read options from the %%! row in the script and add them as args + */ + + append_shebang_args(scriptname); + + /* + * Push the script name and everything following it as extra arguments. + */ + + PUSH3("-run", "escript", "start"); + + /* + * Push all options (without the hyphen) before the script name. + */ + + while (first_opt != last_opt) { + PUSH(first_opt[1]+1); + first_opt++; + } + + PUSH("-extra"); + PUSH(scriptname); + while (argc > 1) { + PUSH(argv[1]); + argc--, argv++; + } + + /* + * Move up the commands for invoking the emulator and adjust eargv + * accordingly. + */ + + while (--eargc_base >= 0) { + UNSHIFT(eargv_base[eargc_base]); + } + + /* + * Invoke Erlang with the collected options. + */ + + PUSH(NULL); + return run_erlang(eargv[0], eargv); +} + +static void +push_words(char* src) +{ + char sbuf[1024]; + char* dst; + + dst = sbuf; + while ((*dst++ = *src++) != '\0') { + if (isspace((int)*src)) { + *dst = '\0'; + PUSH(strsave(sbuf)); + dst = sbuf; + do { + src++; + } while (isspace((int)*src)); + } + } + if (sbuf[0]) + PUSH(strsave(sbuf)); +} +#ifdef __WIN32__ +char *make_commandline(char **argv) +{ + static char *buff = NULL; + static int siz = 0; + int num = 0; + char **arg, *p; + + if (*argv == NULL) { + return ""; + } + for (arg = argv; *arg != NULL; ++arg) { + num += strlen(*arg)+1; + } + if (!siz) { + siz = num; + buff = emalloc(siz*sizeof(char)); + } else if (siz < num) { + siz = num; + buff = realloc(buff,siz*sizeof(char)); + } + p = buff; + for (arg = argv; *arg != NULL; ++arg) { + strcpy(p,*arg); + p+=strlen(*arg); + *p++=' '; + } + *(--p) = '\0'; + + if (debug) { + printf("Processed commandline:%s\n",buff); + } + return buff; +} + +int my_spawnvp(char **argv) +{ + STARTUPINFO siStartInfo; + PROCESS_INFORMATION piProcInfo; + DWORD ec; + + memset(&siStartInfo,0,sizeof(STARTUPINFO)); + siStartInfo.cb = sizeof(STARTUPINFO); + siStartInfo.dwFlags = STARTF_USESTDHANDLES; + siStartInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + siStartInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + siStartInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); + + if (!CreateProcess(NULL, + make_commandline(argv), + NULL, + NULL, + TRUE, + 0, + NULL, + NULL, + &siStartInfo, + &piProcInfo)) { + return -1; + } + CloseHandle(piProcInfo.hThread); + + WaitForSingleObject(piProcInfo.hProcess,INFINITE); + if (!GetExitCodeProcess(piProcInfo.hProcess,&ec)) { + return 0; + } + return (int) ec; +} +#endif /* __WIN32__ */ + + +static int +run_erlang(char* progname, char** argv) +{ +#ifdef __WIN32__ + int status; +#endif + + if (debug) { + int i = 0; + while (argv[i] != NULL) + printf(" %s", argv[i++]); + printf("\n"); + } + +#ifdef __WIN32__ + /* + * Alas, we must wait here for the program to finish. + * Otherwise, the shell from which we was executed will think + * we are finished and print a prompt and read keyboard input. + */ + + status = my_spawnvp(argv)/*_spawnvp(_P_WAIT,progname,argv)*/; + if (status == -1) { + fprintf(stderr, "escript: Error executing '%s': %d", progname, + GetLastError()); + } + return status; +#else + execvp(progname, argv); + error("Error %d executing \'%s\'.", errno, progname); + return 2; +#endif +} + +static void +error(char* format, ...) +{ + char sbuf[1024]; + va_list ap; + + va_start(ap, format); + vsprintf(sbuf, format, ap); + va_end(ap); + fprintf(stderr, "escript: %s\n", sbuf); + exit(1); +} + +static char* +emalloc(size_t size) +{ + char *p = malloc(size); + if (p == NULL) + error("Insufficient memory"); + return p; +} + +static void +efree(void *p) +{ + free(p); +} + +static char* +strsave(char* string) +{ + char* p = emalloc(strlen(string)+1); + strcpy(p, string); + return p; +} + +static char* +get_default_emulator(char* progname) +{ + char sbuf[MAXPATHLEN]; + char* s; + + strcpy(sbuf, progname); + for (s = sbuf+strlen(sbuf); s >= sbuf; s--) { + if (IS_DIRSEP(*s)) { + strcpy(s+1, ERL_NAME); +#ifdef __WIN32__ + if (_access(sbuf, 0) != -1) { + return strsave(sbuf); + } +#else + if (access(sbuf, 1) != -1) { + return strsave(sbuf); + } +#endif + break; + } + } + return ERL_NAME; +} + +#ifdef __WIN32__ +static char* +possibly_quote(char* arg) +{ + int mustQuote = FALSE; + int n = 0; + char* s; + char* narg; + + if (arg == NULL) { + return arg; + } + + /* + * Scan the string to find out if it needs quoting and return + * the original argument if not. + */ + + for (s = arg; *s; s++, n++) { + switch(*s) { + case ' ': + mustQuote = TRUE; + continue; + case '"': + mustQuote = TRUE; + n++; + continue; + case '\\': + if(s[1] == '"') + n++; + continue; + default: + continue; + } + } + if (!mustQuote) { + return arg; + } + + /* + * Insert the quotes and put a backslash in front of every quote + * inside the string. + */ + + s = narg = emalloc(n+2+1); + for (*s++ = '"'; *arg; arg++, s++) { + if (*arg == '"' || (*arg == '\\' && arg[1] == '"')) { + *s++ = '\\'; + } + *s = *arg; + } + if (s[-1] == '\\') { + *s++ ='\\'; + } + *s++ = '"'; + *s = '\0'; + return narg; +} +#endif /* __WIN32__ */ |