/*******************************************************************************
 * Copyright (c) 2006, 2015 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at 
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Andrew Niefer
 *     Red Hat, Inc - Bug 379102 - Prevent running Eclipse as root (optionally)
 *     Rapicorp, Inc - Bug 461728 - [Mac] Allow users to specify values in eclipse.ini outside of the installation
 *******************************************************************************/
 
#include "eclipseUnicode.h"
#include "eclipseCommon.h"
#include "eclipseConfig.h"

#ifdef _WIN32
#include <direct.h>
#else
#include <unistd.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <locale.h>
#include <sys/stat.h>

#include "eclipse-memcpy.h"

static _TCHAR* libraryMsg =
_T_ECLIPSE("The %s executable launcher was unable to locate its \n\
companion shared library.");

static _TCHAR* entryMsg =
_T_ECLIPSE("There was a problem loading the shared library and \n\
finding the entry point.");

static _TCHAR* rootMsg =
_T_ECLIPSE("The %s executable launcher is configured to not start with \n\
administrative privileges.");

#define NAME         _T_ECLIPSE("-name")
#define VMARGS       _T_ECLIPSE("-vmargs")		/* special option processing required */
/* New arguments have the form --launcher.<arg> to avoid collisions */
#define LIBRARY		  _T_ECLIPSE("--launcher.library")
#define SUPRESSERRORS _T_ECLIPSE("--launcher.suppressErrors")
#define INI			  _T_ECLIPSE("--launcher.ini")
#define PROTECT	      _T_ECLIPSE("-protect")	/* This argument is also handled in eclipse.c for Mac specific processing */
#define ROOT		  _T_ECLIPSE("root")		/* the only level of protection we care now */

/* this typedef must match the run method in eclipse.c */
typedef int (*RunMethod)(int argc, _TCHAR* argv[], _TCHAR* vmArgs[]);
typedef void (*SetInitialArgs)(int argc, _TCHAR*argv[], _TCHAR* library);

static _TCHAR*  name          = NULL;			/* program name */
static _TCHAR** userVMarg     = NULL;     		/* user specific args for the Java VM */
static _TCHAR*  programDir	  = NULL;			/* directory where program resides */
static _TCHAR*  officialName  = NULL;
static int      suppressErrors = 0;				/* supress error dialogs */
static int      protectRoot      = 0;				/* check if launcher was run as root, currently works only on Linux/UNIX platforms */

static int 	 	createUserArgs(int configArgc, _TCHAR **configArgv, int *argc, _TCHAR ***argv);
static void  	parseArgs( int* argc, _TCHAR* argv[], int handleVMArgs );
static _TCHAR* 	getDefaultOfficialName(_TCHAR* program);
static _TCHAR*  findProgram(_TCHAR* argv[]);
static _TCHAR*  findLibrary(_TCHAR* library, _TCHAR* program);
static _TCHAR*  checkForIni(int argc, _TCHAR* argv[]);
static _TCHAR*  getDirFromProgram(_TCHAR* program);
static int  isRoot();
 
static int initialArgc;
static _TCHAR** initialArgv;

_TCHAR* eclipseLibrary = NULL; /* path to the eclipse shared library */

#ifdef UNICODE
extern int main(int, char**);
int mainW(int, wchar_t**);
int wmain( int argc, wchar_t** argv ) {
	return mainW(argc, argv);
}

int main(int argc, char* argv[]) {
	/*
	* Run the UNICODE version, convert the arguments from MBCS to UNICODE
	*/
	int i, result;
	wchar_t **newArgv = malloc((argc + 1) * sizeof(wchar_t *));
	for (i=0; i<argc; i++) {
		char *oldArg = argv[i];
		int numChars = MultiByteToWideChar(CP_ACP, 0, oldArg, -1, NULL, 0);
		wchar_t *newArg  = malloc((numChars + 1) * sizeof(wchar_t));
		newArg[numChars] = 0;
		MultiByteToWideChar(CP_ACP, 0, oldArg, -1, newArg, numChars);
		newArgv[i] = newArg;
	}
	newArgv[i] = NULL;
	result = mainW(argc, newArgv);
	for (i=0; i<argc; i++) {
		free(newArgv[i]);
	}
	free(newArgv);
	return result;
}

#define main mainW
#endif /* UNICODE */

int main( int argc, _TCHAR* argv[] )
{
	_TCHAR*  errorMsg;
	_TCHAR*  program;
	_TCHAR*  iniFile;
	_TCHAR*  ch;
	_TCHAR** configArgv = NULL;
	int 	 configArgc = 0;
	int      exitCode = 0;
	int      ret = 0;
	void *	 handle = 0;
	RunMethod 		runMethod;
	SetInitialArgs  setArgs;
	
	setlocale(LC_ALL, "");
	
	initialArgc = argc;
	initialArgv = malloc((argc + 1) * sizeof(_TCHAR*));
	memcpy(initialArgv, argv, (argc + 1) * sizeof(_TCHAR*));
	
	/* 
	 * Strip off any extroneous <CR> from the last argument. If a shell script
	 * on Linux is created in DOS format (lines end with <CR><LF>), the C-shell
	 * does not strip off the <CR> and hence the argument is bogus and may 
	 * not be recognized by the launcher or eclipse itself.
	 */
	 ch = _tcschr( argv[ argc - 1 ], _T_ECLIPSE('\r') );
	 if (ch != NULL)
	 {
	     *ch = _T_ECLIPSE('\0');
	 }
	 
	 /* Determine the full pathname of this program. */
	 program = findProgram(argv);
    
    /* Parse configuration file arguments */
    iniFile = checkForIni(argc, argv);
    if (iniFile != NULL)
		ret = readConfigFile(iniFile, &configArgc, &configArgv);
    else
		ret = readIniFile(program, &configArgc, &configArgv);
	if (ret == 0)
	{
		parseArgs (&configArgc, configArgv, 0);
	}
	
	/* Parse command line arguments           */
    /* Overrides configuration file arguments */
    parseArgs( &argc, argv, 1);
    
    /* Special case - user arguments specified in the config file
	 * are appended to the user arguments passed from the command line.
	 */
	if (configArgc > 0)
	{	
		createUserArgs(configArgc, configArgv, &argc, &argv);
	}
	
	/* Initialize official program name */
	officialName = name != NULL ? _tcsdup( name ) : getDefaultOfficialName(program);
	
	/* Find the directory where the Eclipse program is installed. */
    programDir = getDirFromProgram(program);

	/* Find the eclipse library */
    eclipseLibrary = findLibrary(eclipseLibrary, program);

    /* root check */
	if(protectRoot && isRoot()){
		errorMsg = malloc( (_tcslen(rootMsg) + _tcslen(officialName) + 10) * sizeof(_TCHAR) );
		_stprintf( errorMsg, rootMsg, officialName );
        if (!suppressErrors)
        	displayMessage( officialName, errorMsg );
        else
        	_ftprintf(stderr, _T_ECLIPSE("%s:\n%s\n"), officialName, errorMsg);
        free( errorMsg );
        exit( 2 );
	}
		
	if(eclipseLibrary != NULL)
		handle = loadLibrary(eclipseLibrary);
	if(handle == NULL) {
		errorMsg = malloc( (_tcslen(libraryMsg) + _tcslen(officialName) + 10) * sizeof(_TCHAR) );
        _stprintf( errorMsg, libraryMsg, officialName );
        if (!suppressErrors)
        	displayMessage( officialName, errorMsg );
        else
        	_ftprintf(stderr, _T_ECLIPSE("%s:\n%s\n"), officialName, errorMsg);
        free( errorMsg );
    	exit( 1 );
	}

	setArgs = (SetInitialArgs)findSymbol(handle, SET_INITIAL_ARGS);
	if(setArgs != NULL)
		setArgs(initialArgc, initialArgv, eclipseLibrary);
	else {
		if(!suppressErrors)
			displayMessage(officialName, entryMsg);
		else 
			_ftprintf(stderr, _T_ECLIPSE("%s:\n%s\n"), officialName, entryMsg);
		exit(1);
	}
	
	runMethod = (RunMethod)findSymbol(handle, RUN_METHOD);
	if(runMethod != NULL)
		exitCode = runMethod(argc, argv, userVMarg);
	else { 
		if(!suppressErrors)
			displayMessage(officialName, entryMsg);
		else 
			_ftprintf(stderr, _T_ECLIPSE("%s:\n%s\n"), officialName, entryMsg);
		exit(1);
	}
	unloadLibrary(handle);
	
	free( eclipseLibrary );
    free( programDir );
    free( program );
    free( officialName );
    
	return exitCode;
}

_TCHAR* getProgramPath() {
	return NULL;
}

static _TCHAR* findProgram(_TCHAR* argv[]) {
	_TCHAR * program;
#ifdef _WIN32
	 /* windows, make sure we are looking for the .exe */
	_TCHAR * ch;
	int length = _tcslen(argv[0]);
	ch = malloc( (length + 5) * sizeof(_TCHAR));
	_tcscpy(ch, argv[0]);
	 
	if (length <= 4 || _tcsicmp( &ch[ length - 4 ], _T_ECLIPSE(".exe") ) != 0)
		_tcscat(ch, _T_ECLIPSE(".exe"));
	
	program = findCommand(ch);
	if (ch != program)
		free(ch);
#else
	program = findCommand( argv[0] );
#endif
    if (program == NULL)
    {
#ifdef _WIN32
    	program = malloc( MAX_PATH_LENGTH + 1 );
    	GetModuleFileName( NULL, program, MAX_PATH_LENGTH );
    	argv[0] = program;
#else
    	program = malloc( (strlen( argv[0] ) + 1) * sizeof(_TCHAR) );
    	strcpy( program, argv[0] );
#endif
    } else if (_tcscmp(argv[0], program) != 0) {
    	argv[0] = program;
    }
    return program;
}

/*
 * Parse arguments of the command.
 */
static void parseArgs( int* pArgc, _TCHAR* argv[], int useVMargs )
{
    int     index;

    /* Ensure the list of user argument is NULL terminated. */
    argv[ *pArgc ] = NULL;

	/* For each user defined argument */
    for (index = 0; index < *pArgc; index++){
        if(_tcsicmp(argv[index], VMARGS) == 0) {
        	if (useVMargs == 1)	{ //Use the VMargs as the user specified vmArgs
        		userVMarg = &argv[ index+1 ];
        	}
            argv[ index ] = NULL;
            *pArgc = index;
        } else if(_tcsicmp(argv[index], NAME) == 0) {
        	name = argv[++index];
        } else if(_tcsicmp(argv[index], LIBRARY) == 0) {
        	eclipseLibrary = argv[++index];
        } else if(_tcsicmp(argv[index], SUPRESSERRORS) == 0) {
        	suppressErrors = 1;
        } else if(_tcsicmp(argv[index], PROTECT) == 0) {
        	if(_tcsicmp(argv[++index], ROOT) == 0){
        		protectRoot = 1;
        	}
        }
    }
}

/* We need to look for --launcher.ini before parsing the other args */
static _TCHAR* checkForIni(int argc, _TCHAR* argv[]) 
{
	int index;
	for(index = 0; index < (argc - 1); index++) {
		if(_tcsicmp(argv[index], INI) == 0) {
        	return argv[++index];
        } 
	}
	return NULL;
}

/*
 * Create a new array containing user arguments from the config file first and
 * from the command line second.
 * Allocate an array large enough to host all the strings passed in from
 * the argument configArgv and argv. That array is passed back to the
 * argv argument. That array must be freed with the regular free().
 * Note that both arg lists are expected to contain the argument 0 from the C
 * main method. That argument contains the path/executable name. It is
 * only copied once in the resulting list.
 *
 * Returns 0 if success.
 */
static int createUserArgs(int configArgc, _TCHAR **configArgv, int *argc, _TCHAR ***argv)
{
	_TCHAR** newArray = (_TCHAR **)malloc((configArgc + *argc + 1) * sizeof(_TCHAR *));

	newArray[0] = (*argv)[0];	/* use the original argv[0] */
	memcpy(newArray + 1, configArgv, configArgc * sizeof(_TCHAR *));	
	
	/* Skip the argument zero (program path and name) */
	memcpy(newArray + 1 + configArgc, *argv + 1, (*argc - 1) * sizeof(_TCHAR *));

	/* Null terminate the new list of arguments and return it. */	 
	*argv = newArray;
	*argc += configArgc;
	(*argv)[*argc] = NULL;
	
	return 0;
}

/* Determine the Program Directory
 *
 * This function takes the directory where program executable resides and
 * determines the installation directory.
 */
_TCHAR* getDirFromProgram(_TCHAR* program)
{
	_TCHAR*  ch;
	
	if(programDir != NULL)
		return programDir;

    programDir = malloc( (_tcslen( program ) + 1) * sizeof(_TCHAR) );
    _tcscpy( programDir, program );
    ch = lastDirSeparator( programDir );
	if (ch != NULL)
    {
    	*(ch+1) = _T_ECLIPSE('\0');
   		return programDir;
    }

	/* Can't figure out from the program, lets use the cwd */
	free(programDir);
	programDir = malloc( MAX_PATH_LENGTH * sizeof (_TCHAR));
	_tgetcwd( programDir, MAX_PATH_LENGTH );
	return programDir;
}

_TCHAR* getProgramDir()
{
	return programDir;
}

_TCHAR* getOfficialName() {
	return officialName;
}

/*
 * Determine the default official application name
 *
 * This function provides the default application name that appears in a variety of
 * places such as: title of message dialog, title of splash screen window
 * that shows up in Windows task bar.
 * It is computed from the name of the launcher executable and
 * by capitalizing the first letter. e.g. "c:/ide/eclipse.exe" provides
 * a default name of "Eclipse".
 */
static _TCHAR* getDefaultOfficialName(_TCHAR* program)
{
	_TCHAR *ch = NULL;
	
	/* Skip the directory part */
	ch = lastDirSeparator( program );
	if (ch == NULL) ch = program;
	else ch++;
	
	ch = _tcsdup( ch );
#ifdef _WIN32
	{
		/* Search for the extension .exe and cut it */
		_TCHAR *extension = _tcsrchr(ch, _T_ECLIPSE('.'));
		if (extension != NULL) 
		{
			*extension = _T_ECLIPSE('\0');
		}
	}
#endif
	/* Upper case the first character */
#ifndef LINUX
	{
		*ch = _totupper(*ch);
	}
#else
	{
		if (*ch >= 'a' && *ch <= 'z')
		{
			*ch -= 32;
		}
	}
#endif
	return ch;
}

static _TCHAR* findLibrary(_TCHAR* library, _TCHAR* program) 
{
	_TCHAR* c;
	_TCHAR* path;
	_TCHAR* fragment;
	_TCHAR* result;
	_TCHAR* dot = _T_ECLIPSE(".");
	size_t progLength, pathLength;
	size_t fragmentLength;
	struct _stat stats;
	
	if (library != NULL) {
		path = checkPath(library, programDir, 1);
		if (_tstat(path, &stats) == 0 && (stats.st_mode & S_IFDIR) != 0) 
        {
            /* directory, find the highest version eclipse_* library */
            result = findFile(path, _T_ECLIPSE("eclipse"));
        } else {
        	/* file, return it */
        	result = _tcsdup(path);
        }

		if (path != library)
			free(path);
		return result;
	}
	
	/* build the equinox.launcher fragment name */
	fragmentLength = _tcslen(DEFAULT_EQUINOX_STARTUP) + 1 + _tcslen(wsArg) + 1 + _tcslen(osArg) + 1 + _tcslen(osArchArg) + 1;
	fragment = malloc(fragmentLength * sizeof(_TCHAR));
	_tcscpy(fragment, DEFAULT_EQUINOX_STARTUP);
	_tcscat(fragment, dot);
	_tcscat(fragment, wsArg);
	_tcscat(fragment, dot);
	_tcscat(fragment, osArg);
	//!(fragmentOS.equals(Constants.OS_MACOSX) && !Constants.ARCH_X86_64.equals(fragmentArch))
#if !(defined(MACOSX) && !defined(__x86_64__)) 
	/* The Mac fragment covers both archs and does not have that last segment */
	_tcscat(fragment, dot);
	_tcscat(fragment, osArchArg);
#endif	
	progLength = pathLength = _tcslen(programDir);
#ifdef MACOSX
	pathLength += 9;
#endif
	path = malloc( (pathLength + 1 + 7 + 1) * sizeof(_TCHAR));
	_tcscpy(path, programDir);
	if (!IS_DIR_SEPARATOR(path[progLength - 1])) {
		path[progLength] = dirSeparator;
		path[progLength + 1] = 0;
	}
#ifdef MACOSX
	_tcscat(path, _T_ECLIPSE("../../../"));
#endif
	_tcscat(path, _T_ECLIPSE("plugins"));
	
	c = findFile(path, fragment);
	free(fragment);
	if (c == NULL)
		return c;
	fragment = c;
	
	result = findFile(fragment, _T_ECLIPSE("eclipse"));
	
	free(fragment);
	free(path);
	
	return result; 
}

static int isRoot(){
#ifdef LINUX
	return geteuid() == 0;
#endif
	return 0;
}
