/***********************************************************************
 * 
 *  Copyright (C) 2006 Novell, Inc. All Rights Reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; version 2.1
 *  of the License.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, Novell, Inc.
 * 
 *  To contact Novell about this file by physical or electronic mail, 
 *  you may find current contact information at www.novell.com.
 * 
 *  Author: Greg Richardson <grichardson@novell.com>
 *  
 ***********************************************************************/


// CommandLauncher.cpp : Defines the entry point for the application.
//

#include "stdafx.h"
#include "CommandLauncher.h"
#include "string.h"
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <process.h>
#include <errno.h>

WCHAR **    G_rgArgs; // Command line arguments
int         G_cArg; // Count of command line arguments
//FILE *    G_pf; 

// Forward declarations of functions included in this code module:
int processArguments(LPTSTR lpCmdLine);
void freeArgs();
int countArgs(LPTSTR lpCmdLine);
int addArg(int iArg, LPTSTR lpString, int cChar);
void log(LPTSTR szMessage);
_TCHAR * errorMessage(int err);
int executeCommand( int cArg, _TCHAR* rgArg[]);


#define ERROR_NO_ERROR                    0
#define ERROR_MEMORY_ALLOCATION_FAILED       -1
#define ERROR_INVALID_NUMBER_OF_PARAMETERS      -2
#define ERROR_EXEC_E2BIG                  -3
#define ERROR_EXEC_EACCES                 -4 
#define ERROR_EXEC_EINVAL                 -5
#define ERROR_EXEC_EMFILE                 -6
#define ERROR_EXEC_ENOENT                 -7
#define ERROR_EXEC_ENOEXEC                -8
#define ERROR_EXEC_ENOMEM                 -9
#define ERROR_EXEC_UNKNOWN                -10
#define ERROR_STRCPY_FAILED                  -11
#define ERROR_JAVA_EXE_ARG_MISSING           -12
#define ERROR_JAVA_CLASSPATH_OPTION_ARG_MISSING -13
#define ERROR_JAVA_CLASSPATH_ARG_MISSING     -14
#define ERROR_BAD_COMMAND_LINE               -15



int APIENTRY _tWinMain(HINSTANCE hInstance,
                HINSTANCE hPrevInstance,
                LPTSTR    lpCmdLine,
                int       nCmdShow)
{
   UNREFERENCED_PARAMETER(hInstance);
   UNREFERENCED_PARAMETER(hPrevInstance);
   UNREFERENCED_PARAMETER(lpCmdLine);
   UNREFERENCED_PARAMETER(nCmdShow);

   int rc;

   //_wfopen_s(&G_pf, L"C:\\CommandLauncher.log", L"a+");

   // Process the command line
   if (ERROR_NO_ERROR != (rc = processArguments(lpCmdLine)))
   {
      return rc;
   }

   rc = executeCommand(G_cArg, G_rgArgs);

   log(errorMessage(rc));

   //fwprintf(G_pf, L"CommandLauncher: return = %d\n", rc);

   //fclose(G_pf);

   return rc;
}


// <java exe path> -cp <classpath> class k1=v1 k2=k2
int processArguments(LPTSTR lpCmdLine)
{
   int      iArg;
   int      iChar;
   int      iAssignment;
   int      iClassStart;
   int      iClassEnd;
   int      iClassPathStart;
   int      iClassPathEnd;
   int      rc;
   bool  fClasspathOptionFound = false;
   int      iKeyStart;
   int      iValueEnd;

   //fwprintf( G_pf, L"current command line = %s\n", lpCmdLine);


   // Validate the command line
   if (NULL == lpCmdLine || (WCHAR)0 == *lpCmdLine)
   {
      return ERROR_BAD_COMMAND_LINE;
   }

   // Count the arguments on the command line.  TThe name of this executable
   // is not included in the count.
   G_cArg = countArgs(lpCmdLine);

   // Make sure we got enough to exec something.  There must be at least the
   // path to jave.exe, the classpath option, the classpath and a class.
   if (G_cArg < 4) 
   {
      return ERROR_INVALID_NUMBER_OF_PARAMETERS;
   } 

   // Allocate an array of wide string for the arguments, add 1 for a NULL at
   // the end of the array
   G_rgArgs = (WCHAR**)malloc((G_cArg + 1) * sizeof(WCHAR *));
   if (NULL == G_rgArgs)
   {
      return ERROR_MEMORY_ALLOCATION_FAILED;
   }

   // Null out the array
   memset(G_rgArgs, 0, (G_cArg + 1) * sizeof(WCHAR *)); 

   // Find the java.exe argument
   iChar = 0;
   for (iChar = 0; 0 != lpCmdLine[iChar + 4]; iChar++)
   {
      if (0 == _wcsnicmp(lpCmdLine + iChar, L".exe", 4))
      {
         break;
      }
   }
   if (0 == lpCmdLine[iChar + 4])
   {
      rc = ERROR_JAVA_EXE_ARG_MISSING;
      goto ErrorOut;
   }

   // Add the java.exe argument
   if (ERROR_NO_ERROR != (rc = addArg(0, lpCmdLine, iChar + 4)))
   {
      goto ErrorOut;
   }

   // Move past the java.exe argument
   iChar += 4;

   // Move past any spaces
   for (;L' ' == lpCmdLine[iChar]; iChar++)
   {
      // Intentionally left blank
   }

   // Find the classpath argument
   for (; 0 != lpCmdLine[iChar + 3]; iChar++)
   {
      if (0 == _wcsnicmp(lpCmdLine + iChar, L"-cp", 3))
      {
         fClasspathOptionFound = true;
         break;
      }
   }
   if (!fClasspathOptionFound)
   {
      rc = ERROR_JAVA_CLASSPATH_OPTION_ARG_MISSING;
      goto ErrorOut;
   }
   if (0 == lpCmdLine[iChar + 3])
   {
      rc = ERROR_JAVA_CLASSPATH_ARG_MISSING;
      goto ErrorOut;
   }

   // Add the classpath option argument
   if (ERROR_NO_ERROR != (rc = addArg(1, L"-cp", iChar + 3)))
   {
      goto ErrorOut;
   }

   // Move past the classpath option argument
   iChar += 3;

   // Move past any spaces
   for (;L' ' == lpCmdLine[iChar]; iChar++)
   {
      // Intentionally left blank
   }

   // The classpath is next.  It can have spaces in it so we need to work 
   // backards from the first key/value pair, or the end of the line if
   // there are no key/value pairs.
   iClassPathStart = iChar;

   // Find the location of the next '='
   for (; 0 != lpCmdLine[iChar] && L'=' != lpCmdLine[iChar]; iChar++)
   {
      // Intentially left blank
   }

   // If there was a key/value pair - move to the start of the key
   if (L'=' == lpCmdLine[iChar])
   {
      iAssignment = iChar;

      // Move back to the previous space.  This should put us at the 
      // beginning of the first key/value pair.  Assume that all args 
      // are property key/value pairs and property keys have no spaces.
      for (; L' ' != lpCmdLine[iChar] && iChar >= 0; iChar--)
      {
         // Intentially left blank
      }
   }

   else
   {
      iAssignment = 0;
      iChar--;
   }

   // Move past any spaces (moving toward the start of the line)
   for (;L' ' == lpCmdLine[iChar]; iChar--)
   {
      // Intentionally left blank
   }

   // This should put us at the end of the class to be executed
   iClassEnd = iChar;

   // Move to the previous space (moving toward the start of the line)
   for (;L' ' != lpCmdLine[iChar]; iChar--)
   {
      // Intentionally left blank
   }

   iClassStart = iChar + 1;

   // Add the class argument
   if (ERROR_NO_ERROR != (rc = addArg(3, lpCmdLine + iClassStart, iClassEnd - iClassStart + 1)))
   {
      goto ErrorOut;
   }

   // Move past any spaces (moving toward the start of the line)
   for (;L' ' == lpCmdLine[iChar]; iChar--)
   {
      // Intentionally left blank
   }

   // This should put us at the end of the classpath
   iClassPathEnd = iChar;

   // Add the class path argument
   if (ERROR_NO_ERROR != (rc = addArg(2, lpCmdLine + iClassPathStart, iClassPathEnd - iClassPathStart + 1)))
   {
      goto ErrorOut;
   }

   // Are the any key/value pairs?
   if (0 != iAssignment)
   {
      iArg = 4;
      while (0 != lpCmdLine[iAssignment])
      {
         iKeyStart = iAssignment;

         // Move back to the previous space.  This should put us at the 
         // beginning of the current next key/value pair.  Assume that all args 
         // are property key/value pairs and property keys have no spaces.
         for (; L' ' != lpCmdLine[iKeyStart] && iKeyStart > 0; iKeyStart--)
         {
            // Intentially left blank
         }
         if (L' ' == lpCmdLine[iKeyStart])
         {
            iKeyStart++;
         }

         // Find the location of the next '='
         iValueEnd = iAssignment + 1;
         for (; 0 != lpCmdLine[iValueEnd] && L'=' != lpCmdLine[iValueEnd]; iValueEnd++)
         {
            // Intentially left blank
         }

         // If there was a property...
         if (L'=' == lpCmdLine[iValueEnd])
         {
            iAssignment = iValueEnd;

            // Move back to the previous space.  This should put us at the 
            // beginning of the next key/value pair.  Assume that all args 
            // are property key/value pairs and property keys have no spaces.
            for (; L' ' != lpCmdLine[iValueEnd] && iValueEnd >= 0; iValueEnd--)
            {
               // Intentially left blank
            }
         }

         else
         {
            // We have reached the end of the command line - back off from the 
            // null terminator.
            iAssignment = iValueEnd;
            iValueEnd--;
         }

         // Move thorugh any spaces
         for (; L' ' == lpCmdLine[iValueEnd] && iValueEnd >= 0; iValueEnd--)
         {
            // Intentially left blank
         }

         // Add the key/value pair
         if (ERROR_NO_ERROR != (rc = addArg(iArg, lpCmdLine + iKeyStart, iValueEnd - iKeyStart + 1)))
         {
            goto ErrorOut;
         }

         // Go on to the next arg
         iArg++;
      }
   }

   return ERROR_NO_ERROR;

ErrorOut:

   freeArgs();
   log(errorMessage(rc));
   return rc;
}

void freeArgs()
{
   int iArg;
   if (NULL != G_rgArgs)
   {
      for (iArg = 0; iArg < G_cArg; iArg++)
      {
         if (NULL != G_rgArgs[iArg])
         {
            free(G_rgArgs[iArg]);
            G_rgArgs[iArg] = NULL;
         }
      }
      free(G_rgArgs);
      G_rgArgs = NULL;
   }
}

int countArgs(LPTSTR lpCmdLine)
{
   int cArgument;

   // Check if the exe to execute is the only argument.  Assume that all additional
   // arguments have an '=' in them.
   for (cArgument = 4; *lpCmdLine != (WCHAR)0; lpCmdLine++)
   {
      if (*lpCmdLine == L'=')
      {
         cArgument++;
      }
   }
   return cArgument;
}

int addArg(int iArg, LPTSTR lpString, int cChar)
{
   int cb = (cChar + 3) * sizeof(WCHAR); // count of bytes 

   // Allocate space for the new arg
   G_rgArgs[iArg] = (WCHAR *)malloc(cb);
   if (NULL == G_rgArgs[iArg])
   {
      return ERROR_MEMORY_ALLOCATION_FAILED;
   }

   // Null out the argument
   memset(G_rgArgs[iArg], 0, cb); 

   // Add a starting quote
// G_rgArgs[iArg][0] = L'\"';

   // Copy the arg
   if (0 != wcsncpy_s(G_rgArgs[iArg], cChar + 1, lpString, cChar))
   {
      return ERROR_STRCPY_FAILED;
   }

   // Add a terminating quote
// G_rgArgs[iArg][cb-1] = L'\"';

   return ERROR_NO_ERROR;
}


void log(LPTSTR szMessage)
{
   /*
   LPTSTR szT = L"";
   if (NULL == szMessage)
      szMessage = szT;
   fwprintf(G_pf, L"JavaLauncher: %s\n", szMessage);
   */
}

_TCHAR * errorMessage(int err)
{
   switch (err)
   {
   case ERROR_NO_ERROR:
      return L"No error\n";
   case ERROR_MEMORY_ALLOCATION_FAILED:
      return L"Memory allocation failed\n";
   case ERROR_INVALID_NUMBER_OF_PARAMETERS:
      return L"Invalid number of parameters\n";
   case ERROR_EXEC_E2BIG:
      return L"_exec: The space required for the arguments and environment settings exceeds 32 KB.\n";
   case ERROR_EXEC_EACCES:
      return L"_exec: The specified file has a locking or sharing violation.\n";
   case ERROR_EXEC_EINVAL:
      return L"_exec: Invalid parameter.\n";
   case ERROR_EXEC_EMFILE: 
      return L"_exec: Too many files open (the specified file must be opened to determine whether it is executable).\n";
   case ERROR_EXEC_ENOENT: 
      return L"_exec: The file or path not found.\n";
   case ERROR_EXEC_ENOEXEC:   
      return L"_exec: The specified file is not executable or has an invalid executable-file format.\n";
   case ERROR_EXEC_ENOMEM: 
      return L"_exec: Not enough memory is available to execute the new process; the available memory has been corrupted; or an invalid block exists, indicating that the calling process was not allocated properly.\n";
   case ERROR_EXEC_UNKNOWN:
      return L"Unknown _exec error.\n";
   case ERROR_STRCPY_FAILED:
      return L"String copy failed.\n";
   case ERROR_JAVA_CLASSPATH_OPTION_ARG_MISSING:
      return L"Classpath option \"-cp\" missing\n";
   case ERROR_JAVA_CLASSPATH_ARG_MISSING:
      return L"Classpath argument missing\n";
   case ERROR_BAD_COMMAND_LINE:
      return L"Bad command line\n";
   default:
      return L"Unknown error.\n";
   }
}

int executeCommand( int cArg, _TCHAR* rgArg[] )
{
   int i;                  // Looping variable
   int rc = ERROR_NO_ERROR;   // Return code

   //fwprintf( G_pf, L"Arg count = %d\n", cArg);
   //for (i = 0; i < cArg; i++)
   //{
   // fwprintf(G_pf, L"rgArg[%d] (%s)\n", i, rgArg[i]);
   //}

   // exec the command
// if (-1 == _wexecv( rgArg[0], rgArg))
   if (-1 == _wspawnv(_P_WAIT, rgArg[0], rgArg))
   {
      switch (errno)
      {
      case E2BIG:    // The space required for the arguments and environment settings exceeds 32 KB.
         rc = ERROR_EXEC_E2BIG;
         break;
   
      case EACCES:   // The specified file has a locking or sharing violation.
         rc = ERROR_EXEC_EACCES;
         break;
 
      case EINVAL:   // Invalid parameter.
         rc = ERROR_EXEC_EINVAL;
         break;
 
      case EMFILE:   // Too many files open (the specified file must be opened to determine whether it is executable).
         rc = ERROR_EXEC_EMFILE;
         break;
 
      case ENOENT:   // The file or path not found.
         rc = ERROR_EXEC_ENOENT;
         break;
 
      case ENOEXEC:  // The specified file is not executable or has an invalid executable-file format.
         rc = ERROR_EXEC_ENOEXEC;
         break;
 
      case ENOMEM:   // Not enough memory is available to execute the new process; the available memory has been 
                  // corrupted; or an invalid block exists, indicating that the calling process was not allocated 
                  // properly.
         rc = ERROR_EXEC_ENOMEM;
         break;
 
      default:
         rc = ERROR_EXEC_UNKNOWN;
         break;
      }
   }

   //fwprintf(G_pf, L"ExecuteCommand returning %d\n", rc);
   return rc;
}