/*
 * %CopyrightBegin%
 *
 * Copyright Ericsson AB 1997-2020. 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%
 */
/*
 * Purpose: Common compiler front-end.
 */

#ifdef __WIN32__
#  include <winsock2.h>
#else
#  include <stdio.h>
#  include <limits.h>
#  include <stdlib.h>
#  include <signal.h>
#  include <sys/stat.h>
#endif

#include "etc_common.h"
#include "ei.h"

#define NO 0
#define YES 1

#define ASIZE(a) (sizeof(a)/sizeof(a[0]))

#ifndef PATH_MAX
# define PATH_MAX 4096
#endif

static int debug = 0;		/* Debug level. */
static int use_server = 0;      /* Use compile server. */
static char* source_file = "<no source>"; /* Source file (last argument). */

static char** eargv_base;	/* Base of vector. */
static char** eargv;		/* First argument for erl. */

static int eargc;		/* Number of arguments in eargv. */

#ifdef __WIN32__
#  define QUOTE(s) possibly_quote(s)
#  define IS_DIRSEP(c) ((c) == '/' || (c) == '\\')
#  define ERL_NAME "erl.exe"
#else
#  define QUOTE(s) s
#  define IS_DIRSEP(c) ((c) == '/')
#  define ERL_NAME "erl"
#endif

#define UNSHIFT(s) eargc++, eargv--; eargv[0] = QUOTE(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)

#ifdef __WIN32__
static int pause_after_execution = 0;
#endif

/*
 * Local functions.
 */

static void get_env_compile_server(void);
static char* process_opt(int* pArgc, char*** pArgv, int offset);
static void error(char* format, ...);
static void* emalloc(size_t size);
#ifdef HAVE_COPYING_PUTENV
static void efree(void *p);
#endif
static char* strsave(char* string);
static void push_words(char* src);
static int run_erlang(char* name, char** argv);
static void call_compile_server(char** argv);
static void encode_env(ei_x_buff* buf);
#ifndef __WIN32__
static char* find_executable(char* progname);
static char* safe_realpath(char* file);
#endif
static char* get_encoding(void);
static char* decode_binary(const char* buf, int* dec_index, int* dec_size);
static void start_compile_server(char* node_name, char** argv);
static char* get_default_emulator(char* progname);
static char* possibly_unquote(char* arg);
#ifdef __WIN32__
static char* possibly_quote(char* arg);
static void* erealloc(void *p, size_t size);
#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;
    wchar_t *wcvalue = NULL;
    wchar_t wckey[256];
    int len; 

    MultiByteToWideChar(CP_UTF8, 0, key, -1, wckey, 256);
    
    while (1) {
	DWORD nsz;
	if (wcvalue)
	    free(wcvalue);
	wcvalue = (wchar_t *) emalloc(size*sizeof(wchar_t));
	SetLastError(0);
	nsz = GetEnvironmentVariableW(wckey, wcvalue, size);
	if (nsz == 0 && GetLastError() == ERROR_ENVVAR_NOT_FOUND) {
	    free(wcvalue);
	    return NULL;
	}
	if (nsz <= size) {
	    len = WideCharToMultiByte(CP_UTF8, 0, wcvalue, -1, NULL, 0, NULL, NULL);
	    value = emalloc(len*sizeof(char));
	    WideCharToMultiByte(CP_UTF8, 0, wcvalue, -1, value, len, NULL, NULL);
	    free(wcvalue);
	    return value;
	}
	size = nsz;
    }
#else
    return getenv(key);
#endif
}

static void
set_env(char *key, char *value)
{
#ifdef __WIN32__
    WCHAR wkey[MAXPATHLEN];
    WCHAR wvalue[MAXPATHLEN];
    MultiByteToWideChar(CP_UTF8, 0, key, -1, wkey, MAXPATHLEN);
    MultiByteToWideChar(CP_UTF8, 0, value, -1, wvalue, MAXPATHLEN);
    if (!SetEnvironmentVariableW(wkey, wvalue))
        error("SetEnvironmentVariable(\"%s\", \"%s\") failed!", key, value);
#else
    size_t size = strlen(key) + 1 + strlen(value) + 1;
    char *str = emalloc(size);
    sprintf(str, "%s=%s", key, value);
    if (putenv(str) != 0)
        error("putenv(\"%s\") failed!", str);
#ifdef HAVE_COPYING_PUTENV
    efree(str);
#endif
#endif
}

static void
free_env_val(char *value)
{
#ifdef __WIN32__
    if (value)
	free(value);
#endif
}

#ifdef __WIN32__
int wmain(int argc, wchar_t **wcargv)
{
    char** argv;
#else
int main(int argc, char** argv)
{
#endif
    int single_scheduler;
    int eargv_size;
    int eargc_base;		/* How many arguments in the base of eargv. */
    char* emulator;
    char *env;

#ifdef __WIN32__
    int i;
    int len;
    /* Convert argv to utf8 */
    argv = emalloc((argc+1) * sizeof(char*));
    for (i=0; i<argc; i++) {
	len = WideCharToMultiByte(CP_UTF8, 0, wcargv[i], -1, NULL, 0, NULL, NULL);
	argv[i] = emalloc(len*sizeof(char));
	WideCharToMultiByte(CP_UTF8, 0, wcargv[i], -1, argv[i], len, NULL, NULL);
    }
    argv[argc] = NULL;
#endif

    get_env_compile_server();

    ei_init();

    env = get_env("ERLC_EMULATOR");
    emulator = env ? env : get_default_emulator(argv[0]);

    if (strlen(emulator) >= MAXPATHLEN)
        error("Value of environment variable ERLC_EMULATOR is too large");

    /*
     * Add scriptname to env
     */

    set_env("ESCRIPT_NAME", argv[0]);

    /*
     * Save a piece of configuration in an environment variable.  The
     * point is that the compile server needs to know that the same
     * Erlang/OTP system would be started. On Unix, we save the full
     * path to the Erlang emulator. On Windows, we save the value of
     * the environment variable PATH. If the compile server finds that
     * another Erlang/OTP system would be started, it will terminate
     * itself.
     */

#ifdef __WIN32__
    set_env("ERLC_CONFIGURATION", get_env("PATH"));
#else
    {
        char* full_path_emulator = find_executable(emulator);
        set_env("ERLC_CONFIGURATION", full_path_emulator);
    }
#endif

    /*
     * 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 adding of emulator options (like -pa)
     * before '-s erlcompile compile_cmdline...'.
     *
     * Oh, by the way, we will push the compiler command in the
     * base of the eargv vector, and move it up later.
     */

    eargv_size = argc*6+100;
    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("+sbtu");
    PUSH("+A0");
    PUSH("-noinput");
    PUSH2("-mode", "minimal");
    PUSH2("-boot", "no_dot_erlang");
    PUSH3("-s", "erl_compile", "compile_cmdline");
    PUSH("-extra");

    /*
     * Push standard arguments to Erlang.
     */

    /*
     * Parse all command line switches.
     */

    single_scheduler = 1;

    while (argc > 1) {

	/*
	 * Options starting with '+' are passed on to Erlang.
	 */

        source_file = "<no source>";
	switch (argv[1][0]) {
	case '+':
	    PUSH(argv[1]);
	    break;
	case '-':
	    switch (argv[1][1]) {
	    case 'd':
		if (argv[1][2] == '\0') {
		    debug++;
		} else {
		    PUSH(argv[1]);
		}
		break;
            case 'n':
                if (strcmp(argv[1], "-no-server") == 0) {
                    use_server = 0;
                } else {
                    PUSH(argv[1]);
                }
		break;
	    case 'p':
		{
		    int c = argv[1][2];
		    
		    if (c != 'a' && c != 'z') {
			PUSH(argv[1]);
#ifdef __WIN32__
		    } else if (strcmp(argv[1], "-pause") == 0) {
			pause_after_execution = 1;
#endif
		    } else {
			char option[4];

			UNSHIFT(process_opt(&argc, &argv, 1));
			option[0] = '-';
			option[1] = 'p';
			option[2] = c;
			option[3] = '\0';
			UNSHIFT(strsave(option));
		    }
		}
		break;
	    case 's':
		if (strcmp(argv[1], "-smp") == 0) {
		    UNSHIFT(argv[1]);
                } else if (strcmp(argv[1], "-server") == 0) {
                    use_server = 1;
		} else {
		    PUSH(argv[1]);
		}
		break;
	    default:
		PUSH(argv[1]);
		break;
	    }
	    break;
	default:
            source_file = argv[1];
	    PUSH(argv[1]);
	    break;
	}
	argc--, argv++;
    }

    /* The compile server benefits from multiple schedulers. */
    single_scheduler = single_scheduler && !use_server;

    if (single_scheduler) {
        /* Limit ourselves to a single scheduler to save memory and avoid
         * starving the system of threads when used in parallel builds (e.g.
         * make -j64 on a 64-core system). */
        UNSHIFT("+S1");
    }

    /*
     * 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);
    if (use_server) {
        call_compile_server(eargv);
    }
    return run_erlang(eargv[0], eargv);
}

static char*
process_opt(int* pArgc, char*** pArgv, int offset)
{
    int argc = *pArgc;
    char** argv = *pArgv;
    int c = argv[1][1];
    
    if (argv[1][2+offset] != '\0') {
	/*
	 * The option was given as -x<value>.
	 */
	return argv[1]+2+offset;
    }

    /*
     * Look at the next argument.
     */

    argc--, argv++;
    if (argc < 2 || argv[1][0] == '-')
	error("No value given to -%c option", c);
    *pArgc = argc;
    *pArgv = argv;
    return argv[1];
}

static void
get_env_compile_server(void)
{
    char* us = get_env("ERLC_USE_SERVER");

    if (us == NULL) {
        return;                 /* Keep default */
    }

    switch (us[0]) {
    case 'f':
        if (strcmp(us+1, "alse") == 0) {
            use_server = 0;
            return;
        }
        break;
    case 'n':
        if (strcmp(us+1, "o") == 0) {
            use_server = 0;
            return;
        }
        break;
    case 't':
        if (strcmp(us+1, "rue") == 0) {
            use_server = 1;
            return;
        }
        break;
    case 'y':
        if (strcmp(us+1, "es") == 0) {
            use_server = 1;
            return;
        }
        break;
    }
    fprintf(stderr, "erlc: Warning: Ignoring unrecognized value '%s' "
            "for environment value ERLC_USE_SERVER\n", us);
}

static void
push_words(char* src)
{
    char sbuf[MAXPATHLEN];
    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__
wchar_t *make_commandline(char **argv)
{
    wchar_t *buff = NULL;
    int siz = 0;
    int num = 0, len;
    char **arg;
    wchar_t *p;

    if (*argv == NULL) { 
	return L"";
    }
    for (arg = argv; *arg != NULL; ++arg) {
	num += strlen(*arg)+1;
    }
    if (!siz) {
	siz = num;
	buff = (wchar_t *) emalloc(siz*sizeof(wchar_t));
    } else if (siz < num) {
	siz = num;
	buff = (wchar_t *) erealloc(buff,siz*sizeof(wchar_t));
    }
    p = buff;
    num=0;
    for (arg = argv; *arg != NULL; ++arg) {
	len = MultiByteToWideChar(CP_UTF8, 0, *arg, -1, p, siz);
	p+=(len-1);
	*p++=L' ';
    }
    *(--p) = L'\0';

    if (debug > 1) {
	fprintf(stderr, "Processed command line: %S\n", buff);
    }
    return buff;
}

int my_spawnvp(int wait, char **argv)
{
    STARTUPINFOW siStartInfo;
    PROCESS_INFORMATION piProcInfo;
    DWORD ec = 0;

    memset(&siStartInfo,0,sizeof(STARTUPINFOW));
    siStartInfo.cb = sizeof(STARTUPINFOW); 
    siStartInfo.dwFlags = STARTF_USESTDHANDLES;
    siStartInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
    siStartInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    siStartInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
    siStartInfo.wShowWindow = SW_HIDE;
    siStartInfo.dwFlags |= STARTF_USESHOWWINDOW;


    if (!CreateProcessW(NULL, 
		       make_commandline(argv),
		       NULL, 
		       NULL, 
		       TRUE, 
		       0,
		       NULL, 
		       NULL, 
		       &siStartInfo, 
		       &piProcInfo)) {
	return -1;
    }
    CloseHandle(piProcInfo.hThread);

    if (wait) {
        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

    ASSERT(progname && argv[0]);

    if (debug > 0) {
        fprintf(stderr, "spawning erl for %s", source_file);
    }
    if (debug > 1) {
	int i = 0;
        fprintf(stderr, ":\n  ");
	while (argv[i] != NULL) {
	    fprintf(stderr, "%s ", argv[i++]);
        }
    }
    if (debug) {
        putc('\n', stderr);
    }

#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(1, argv);
    if (status == -1) {
	fprintf(stderr, "erlc: Error executing '%s': %d", progname, 
		GetLastError());
    }
    if (pause_after_execution) {
	fprintf(stderr, "Press ENTER to continue . . .\n");
	while (getchar() != '\n')
	    ;
    }
    return status;
#else
    execvp(progname, argv);
    error("Error %d executing \'%s\'.", errno, progname);
    return 2;
#endif
}

static void
call_compile_server(char** argv)
{
    ei_cnode ec;
    char* user;
    char* server_id;
    char node_name[MAXNODELEN+1];
    char remote[MAXNODELEN+1];
    short creation = 1;
    int fd;
    char cwd[MAXPATHLEN+1];
    ei_x_buff args;
    ei_x_buff reply;
    int reply_size;
    int dec_size, dec_index;
    char atom[MAXATOMLEN];
    int argc;

#ifdef __WIN32__
    if (_getcwd(cwd, sizeof(cwd)) == 0) {
        fprintf(stderr, "erlc: failed to get current working directory\n");
        exit(2);
    }
#else
    if (getcwd(cwd, sizeof(cwd)) == 0) {
        fprintf(stderr, "erlc: failed to get current working directory\n");
        exit(2);
    }
#endif

#ifndef __WIN32__
    {
        struct sigaction act;

        /*
         * If the node is terminating when ei_rpc() is executed, the process
         * may receive a SIGPIPE signal. Make sure it does not kill this process.
         */
        act.sa_handler = SIG_IGN;
        sigemptyset(&act.sa_mask);
        act.sa_flags = 0;
        sigaction(SIGPIPE, &act, NULL);
    }
#endif

    /* Get user name */
    user = get_env("USERNAME");  /* Windows */
    if (!user) {
        user = get_env("LOGNAME"); /* Unix */
    }
    if (!user) {
        user = get_env("USER");  /* Unix */
    }
    if (!user) {
        user = "nouser";
    }

    /* Get build server id, if any */
    server_id = get_env("ERLC_SERVER_ID");
    if (server_id) {
        /* Filter out obviously wrong characters. Making it waterproof would be
         * too complicated and letting an invalid name through won't have any
         * negative consequences beyond bad performance. */
        if (strpbrk(server_id, ",@.!#$%^&=+*/()[]{}|'\"`~?")) {
            error("ERLC_SERVER_ID must not contain characters that are "
                  "invalid in node names.\n");
        }
    } else {
        server_id = "";
    }

    /* Create my own node name. */
#ifdef __WIN32__
    sprintf(node_name, "erlc_client_%s_%lu", user, (unsigned long) GetCurrentProcessId());
#else
    sprintf(node_name, "erlc_client_%s_%d", user, getpid());
#endif

    if (ei_connect_init(&ec, node_name, "erlc_compile_server_cookie", creation) < 0) {
        /*
         * There is probably no .erlang.cookie file.
         */
        if (debug > 1) {
            fprintf(stderr, "\ncan't create C node %s: %s\n",
                    node_name, strerror(erl_errno));
        }
        if(snprintf(remote, MAXNODELEN, "erl_compile_server_%s_%s@host",
                    server_id, user) >= MAXNODELEN) {
            error("Compile server node name is too long.\n"
                  "\tERL_SERVER_ID = %s\n"
                  "\tuser name = %s\n",
                  server_id, user);
        }
        goto start_compile_server;
    }

    /* Create node name for compile server. */

    if(snprintf(remote, MAXNODELEN, "erl_compile_server_%s_%s@%s",
                server_id, user, ei_thishostname(&ec)) >= MAXNODELEN) {
        error("Compile server node name is too long.\n"
              "\tERL_SERVER_ID = %s\n"
              "\tuser name = %s\n",
              server_id, user);
    }

    if ((fd = ei_connect(&ec, remote)) < 0) {
        if (debug > 1) {
            fprintf(stderr, "failed to connect to compile server %s: %s\n",
                    remote, strerror(erl_errno));
        }
        goto start_compile_server;
    }

    /*
     * Encode the request to the compile server.
     */

    ei_x_new_with_version(&args);
    ei_x_encode_list_header(&args, 1);
    ei_x_encode_map_header(&args, 4);
    ei_x_encode_atom(&args, "encoding");
    ei_x_encode_atom(&args, get_encoding());
    ei_x_encode_atom(&args, "cwd");
    ei_x_encode_string(&args, cwd);
    ei_x_encode_atom(&args, "env");
    encode_env(&args);
    ei_x_encode_atom(&args, "command_line");
    argc = 0;
    while (argv[argc]) {
        ei_x_encode_list_header(&args, 1);
        ei_x_encode_string(&args, possibly_unquote(argv[argc]));
        argc++;
    }
    ei_x_encode_empty_list(&args); /* End of command_line */
    ei_x_encode_empty_list(&args); /* End of argument list for apply */

    /*
     * Do a RPC to the compile server.
     */

    ei_x_new_with_version(&reply);
    reply_size = ei_rpc(&ec, fd, "erl_compile_server", "compile",
                        args.buff+1, args.index-1, &reply);
    if (reply_size < 0) {
        if (debug > 1) {
            fprintf(stderr, "failed to rpc to node %s: %s\n",
                    remote, strerror(erl_errno));
        }
        goto start_compile_server;
    }

    /*
     * Decode the answer.
     */

    dec_index = 0;
    if (ei_decode_atom(reply.buff, &dec_index, atom) == 0 &&
        strcmp(atom, "wrong_config") == 0) {
        if (debug > 1) {
            fprintf(stderr, "wrong configuration\n");
        }
        goto start_compile_server;
    } else if (ei_decode_tuple_header(reply.buff, &dec_index, &dec_size) == 0) {
        atom[0] = '\0';
        if (dec_size >= 2) {
            ei_decode_atom(reply.buff, &dec_index, atom);
        }
        if (dec_size == 2) {
            if (strcmp(atom, "ok") == 0) {
                char* output = decode_binary(reply.buff, &dec_index, &dec_size);
                if (debug) {
                    fprintf(stderr, "called server for %s => ok\n", source_file);
                }
                if (output) {
                    fwrite(output, dec_size, 1, stdout);
                    exit(0);
                }
            }
        } else if (dec_size == 3 && strcmp(atom, "error") == 0) {
            int std_size, err_size;
            char* std;
            char* err;

            if (debug) {
                fprintf(stderr, "called server for %s => error\n", source_file);
            }
            std = decode_binary(reply.buff, &dec_index, &std_size);
            err = decode_binary(reply.buff, &dec_index, &err_size);
            if (std && err) {
                fwrite(err, err_size, 1, stderr);
                fwrite(std, std_size, 1, stdout);
                exit(1);
            }
        }
    }

    /*
     * Unrecognized term, probably because the node was shutting down.
     */

    if (debug > 1) {
        fprintf(stderr, "unrecognized term returned by compilation server:\n");
        dec_index = 0;
        ei_print_term(stderr, reply.buff, &dec_index);
        putc('\n', stderr);
    }

 start_compile_server:
    *strchr(remote, '@') = '\0';
    start_compile_server(remote, argv);
}

static void
encode_env(ei_x_buff* buf)
{
    char* env_names[] = {"ERL_AFLAGS",
                         "ERL_FLAGS",
                         "ERL_ZFLAGS",
                         "ERL_COMPILER_OPTIONS",
                         "ERL_LIBS",
                         "ERLC_CONFIGURATION",
                         0};
    char** p = env_names;
    while (p[0]) {
        char* val;

        if ((val = get_env(p[0])) != 0) {
            ei_x_encode_list_header(buf, 1);
            ei_x_encode_tuple_header(buf, 2);
            ei_x_encode_string(buf, p[0]);
            ei_x_encode_string(buf, val);
        }
        p++;
    }
    ei_x_encode_empty_list(buf);
}

#ifndef __WIN32__
static char*
find_executable(char* progname)
{
    char* path;
    char* start_path;
    char* real_name;
    char buf[PATH_MAX];
    size_t len_component;
    size_t len_prog;

    if (strchr(progname, '/')) {
        return progname;
    }

    len_prog = strlen(progname);

    if (!(path = getenv("PATH"))) {
        path = "/bin:/usr/bin";
    }

    do {
        for (start_path = path; *path != '\0' && *path != ':'; path++) {
            ;
        }
        if (start_path == path) {
            start_path = ".";
            len_component = 1;
        } else {
            len_component = path - start_path;
        }
        memcpy(buf, start_path, len_component);
        buf[len_component] = '/';
        memcpy(buf + len_component + 1, progname, len_prog);
        buf[len_component + len_prog + 1] = '\0';
        if ((real_name = safe_realpath(buf)) != 0) {
            struct stat s;
            if (stat(real_name, &s) == 0 && s.st_mode & S_IFREG) {
                return real_name;
            }
        }
    } while (*path++ == ':');
    return progname;
}

static char*
safe_realpath(char* file)
{
    /*
     * Always allocate a buffer for the result of realpath().
     * realpath() on old versions of MacOS X will crash if the buffer
     * argument is NULL, and realpath() will fail on old versions of
     * Solaris.
     */
    char* real_name = emalloc(PATH_MAX + 1);
    return realpath(file, real_name);
}
#endif

static char*
get_encoding(void)
{
#ifdef __WIN32__
    return "latin1";
#else
    char* p;
    p = get_env("LC_ALL");
    if (!p) {
        p = get_env("LC_CTYPE");
    }
    if (!p) {
        p = get_env("LANG");
    }
    if (!p) {
        return "latin1";
    } else {
        return strstr(p, "UTF-8") ? "utf8" : "latin1";
    }
#endif
}

static char*
decode_binary(const char* buf, int* dec_index, int* dec_size)
{
    int dec_type;
    char* bin;

    ei_get_type(buf, dec_index, &dec_type, dec_size);
    bin = emalloc(*dec_size);
    if (ei_decode_binary(buf, dec_index, bin, NULL) < 0) {
        return NULL;
    }
    return bin;
}

static void
start_compile_server(char* node_name, char** argv)
{
    char* eargv[100];
    int eargc = 0;
    char* progname = argv[0];

    while (strcmp(argv[0], "-mode") != 0) {
        eargv[eargc++] = *argv++;
    }
    PUSH2("-boot", "no_dot_erlang");
    PUSH2("-sname", node_name);
    PUSH2("-setcookie", "erlc_compile_server_cookie");
    PUSH("-hidden");
    PUSH("-detached");
    PUSH3("-kernel", "start_compile_server", "true");

    /*
     * If this is an older Erlang system (before 22.1) that does not
     * support the compile server, terminate immediately.
     */
    PUSH2("-eval", "is_pid(whereis(erl_compile_server)) orelse halt(1)");

    PUSH(NULL);

    if (debug == 1) {
        fprintf(stderr, "starting compile server %s\n", node_name);
    } else if (debug > 1) {
	int i = 0;
        fprintf(stderr, "starting compile server %s:\n", node_name);
	while (eargv[i] != NULL) {
	    fprintf(stderr, "%s ", eargv[i++]);
        }
        putc('\n', stderr);
    }

    ASSERT(eargv[0]);
#ifdef __WIN32__
    if (my_spawnvp(0, eargv) == -1) {
	fprintf(stderr, "erlc: Error executing '%s': %d", progname,
		GetLastError());
    }
#else
    if (fork() == 0) {
        execvp(eargv[0], eargv);
        error("Error %d executing \'%s\'.", errno, progname);
    }
#endif
}

static void
error(char* format, ...)
{
    char sbuf[1024];
    va_list ap;
    
    va_start(ap, format);
    erts_vsnprintf(sbuf, sizeof(sbuf), format, ap);
    va_end(ap);
    fprintf(stderr, "erlc: %s\n", sbuf);
    exit(1);
}

static void*
emalloc(size_t size)
{
  void *p = malloc(size);
  if (p == NULL)
    error("Insufficient memory");
  return p;
}

#ifdef __WIN32__
static void *
erealloc(void *p, size_t size)
{
    void *res = realloc(p, size);
    if (res == NULL)
    error("Insufficient memory");
    return res;
}
#endif

#ifdef HAVE_COPYING_PUTENV
static void
efree(void *p)
{
    free(p);
}
#endif
static char*
strsave(char* string)
{
  char* p = emalloc(strlen(string)+1);
  strcpy(p, string);
  return p;
}

static int 
file_exists(char *progname) 
{
#ifdef __WIN32__
    wchar_t wcsbuf[MAXPATHLEN];
    MultiByteToWideChar(CP_UTF8, 0, progname, -1, wcsbuf, MAXPATHLEN);
    return (_waccess(wcsbuf, 0) != -1);
#else
    return (access(progname, 1) != -1);
#endif
}

static char*
get_default_emulator(char* progname)
{
    char sbuf[MAXPATHLEN];
    char* s;

    if (strlen(progname) >= sizeof(sbuf))
        return ERL_NAME;

    strcpy(sbuf, progname);
    for (s = sbuf+strlen(sbuf); s >= sbuf; s--) {
	if (IS_DIRSEP(*s)) {
	    strcpy(s+1, ERL_NAME);
	    if(file_exists(sbuf))
		return strsave(sbuf);
	    break;
	}
    }
    return ERL_NAME;
}


static char*
possibly_unquote(char* arg)
{
#ifndef __WIN32__
    /* Nothing to do if not Windows. */
    return arg;
#else
    char* unquoted;
    char* dstp;

    if (arg[0] != '"') {
        /* Not quoted. Nothing to do. */
        return arg;
    }

    /*
     * Remove the quotes and remove backslashes before quotes.
     */

    unquoted = emalloc(strlen(arg) + 1);
    arg++;
    dstp = unquoted;
    while (*arg) {
        if (arg[0] == '\\' && arg[1] == '"') {
            *dstp++ = '"';
            arg += 2;
        } else {
            *dstp++ = *arg++;
        }
    }
    *--dstp = 0;
    return unquoted;
#endif
}

#ifdef __WIN32__
static char*
possibly_quote(char* arg)
{
    int mustQuote = NO;
    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 = YES;
	    continue;
	case '"':
	    mustQuote = YES;
	    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__ */
