/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
*
*
*      Copyright 2008 Sun Microsystems, Inc.
*/

#include "winlauncher.h"

// ----------------------------------------------------
// Generates the pid file name for a given instanceDir.
// Returns TRUE if the command name could be initiated and
// FALSE otherwise (buffer overflow because the resulting
// string is bigger than maxSize).
// ----------------------------------------------------
BOOL getPidFile(const char* instanceDir, char* pidFile, unsigned int maxSize)
{
  BOOL returnValue;
  char* relativePath = "\\logs\\server.pid";

  debug("Attempting to get the PID file for instanceDir='%s'", instanceDir);

  if ((strlen(relativePath) + strlen(instanceDir)) < maxSize)
  {
    sprintf(pidFile, "%s\\logs\\server.pid", instanceDir);
    returnValue = TRUE;
    debug("PID file name is '%s'.", pidFile);
  }
  else
  {
    debugError("Unable to get the PID file name because the path was too long.");
    returnValue = FALSE;
  }
  return returnValue;
} // getPidFile


// ----------------------------------------------------
// Tells whether a file exists or not.  If the file exists
// returns TRUE and FALSE otherwise.
// ----------------------------------------------------
BOOL fileExists(const char *fileName)
{
  struct stat finfo;
  BOOL returnValue = FALSE;

  if(stat(fileName, &finfo) < 0)
  {
    returnValue = FALSE;
  }
  else
  {
    returnValue = TRUE;
  }

  debug("File '%s' does%s exist.", fileName, (returnValue ? "" : " not"));

  return returnValue;
} // fileExists


// ----------------------------------------------------
// Deletes the pid file for a given instance directory.
// If the file could be deleted (or it does not exist)
// returns TRUE and FALSE otherwise.
// ----------------------------------------------------
BOOL deletePidFile(const char* instanceDir)
{
  BOOL returnValue = FALSE;
  char pidFile[PATH_SIZE];
  int nTries = 10;

  debug("Attempting to delete the PID file from instanceDir='%s'.", instanceDir);
  // Sometimes the lock on the system in windows takes time to be released.
  if (getPidFile(instanceDir, pidFile, PATH_SIZE))
  {
    while (fileExists(pidFile) && (nTries > 0) && !returnValue)
    {
      debug("PID file '%s' exists, attempting to remove it.", instanceDir);
      if (remove(pidFile) == 0)
      {
        debug("Successfully removed PID file: '%s'.", pidFile);
        returnValue = TRUE;
      }
      else
      {
        nTries--;
        debug("Failed to remove the PID file.  Sleeping for a bit.  Will try %d more time(s).", nTries);
        Sleep(500);
      }
    }
  }

  debug("deletePidFile('%s') returning %d.", instanceDir, returnValue);
  return returnValue;
}  // deletePidFile


// ----------------------------------------------------
// Returns the pid stored in the pid file for a given server
// instance directory.  If the pid could not be retrieved
// it returns 0.
// ----------------------------------------------------
int getPid(const char* instanceDir)
{
  int returnValue;
  char pidFile[PATH_SIZE];
  FILE *f;
  char buf[BUF_SIZE];
  int read;

  debug("Attempting to get the PID for the server rooted at '%s'.", instanceDir);
  if (getPidFile(instanceDir, pidFile, PATH_SIZE))
  {
    if ((f = fopen(pidFile, "r")) != NULL)
    {
      read = fread(buf, 1, sizeof(buf),f);
      debug("Read '%s' from the PID file '%s'.", buf, pidFile);
    }

    if (f != NULL)
    {
      fclose(f);
      returnValue = (int)strtol(buf, (char **)NULL, 10);
    }
    else
    {
      char * msg = "File %s could not be opened.\nMost likely the server has already stopped.\n\n";
      debug(msg, pidFile);
      fprintf(stderr, msg, pidFile);
      returnValue = 0;
    }
  }
  else
  {
    returnValue = 0;
  }
  debug("getPid('%s') returning %d.", instanceDir, returnValue);
  return returnValue;
}  // getPid


// ----------------------------------------------------
// Kills the process associated with the provided pid.
// Returns TRUE if the process could be killed or the
// process did not exist and false otherwise.
// ----------------------------------------------------
BOOL killProcess(int pid)
{
  BOOL processDead;
  HANDLE procHandle;

  debug("killProcess(pid=%d)", pid);

  debug("Opening process with pid=%d.", pid);
  procHandle = OpenProcess(
  PROCESS_TERMINATE               // to terminate the process
  | PROCESS_QUERY_INFORMATION,    // to get exit code
  FALSE,                          // handle is not inheritable
  pid
  );

  if (procHandle == NULL)
  {
    debug("The process with pid=%d has already terminated.", pid);
    // process already dead
    processDead = TRUE;
  }
  else
  {
    if (!TerminateProcess(procHandle, 0))
    {
      debugError("Failed to terminate process (pid=%d) lastError=%d.", pid, GetLastError());
      // failed to terminate the process
      processDead = FALSE;
    }
    else
    {
      DWORD exitCode;
      int nTries = 20;

      debug("Successfully began termination process for (pid=%d).", pid);
      // wait for the process to end.

      processDead = FALSE;
      while ((nTries > 0) && !processDead)
      {
        GetExitCodeProcess(procHandle, &exitCode);
        if (exitCode == STILL_ACTIVE)
        {
          // process is still alive, let's wait 1 sec and loop again
          nTries--;
          debug("Process (pid=%d) has not yet exited.  Sleeping for 1 second and will try %d more time(s).", pid, nTries);
          Sleep(1000);
        }
        else
        {
          debug("Process (pid=%d) has exited with exit code %d.", pid, exitCode);
          processDead = TRUE;
        }
      }
    }
    CloseHandle(procHandle);
  }

  debug("killProcess(pid=%d) returning %d", pid, processDead);
  return processDead;
} // killProcess

// ----------------------------------------------------
// Creates the pid file for a given instance directory.
// and a given pid.
// If the file could be created returns TRUE and FALSE
// otherwise.
// ----------------------------------------------------
BOOL createPidFile(const char* instanceDir, int pid)
{
  BOOL returnValue = FALSE;
  char pidFile[PATH_SIZE];
  FILE *f;

  debug("createPidFile(instanceDir='%s',pid=%d)", instanceDir, pid);

  if (getPidFile(instanceDir, pidFile, PATH_SIZE))
  {
    if ((f = fopen(pidFile, "w")) != NULL)
    {
      fprintf(f, "%d", pid);
      fclose (f);
      returnValue = TRUE;
      debug("Successfully put pid=%d in the pid file '%s'.", pid, pidFile);
    }
    else
    {
      debugError("Couldn't create the pid file '%s' because the file could not be opened.", pidFile);
      returnValue = FALSE;
    }
  }
  else
  {
    debugError("Couldn't create the pid file because the pid file name could not be constructed.");
    returnValue = FALSE;
  }

  return returnValue;
}  // createPidFile


// ----------------------------------------------------
// Elaborate the command line: "cmd arg1 arg2..."
// If an arg contains white space(s) then add " " to protect them
// but don't do it for option (an option starts with -).
// Returns TRUE if the command name could be initiated and
// FALSE otherwise (buffer overflow because the resulting
// string is bigger than maxSize).
// ----------------------------------------------------
BOOL getCommandLine(const char* argv[], char* command, unsigned int maxSize)
{
  int curCmdInd = 0;
  int i = 0;
  BOOL overflow = FALSE;

  debug("Constructing full command line from arguments:");
  for (i = 0; (argv[i] != NULL); i++)
  {
    debug(" argv[%d]: %s", i, argv[i]);
  }

  i = 0;
  while ((argv[i] != NULL) && !overflow)
  {
    const char* curarg = argv[i++];
    if (i > 1)
    {
      if (curCmdInd + strlen(" ") < maxSize)
      {
        sprintf (&command[curCmdInd], " ");
        curCmdInd = strlen(command);
      }
      else
      {
        overflow = TRUE;
      }
    }

    if (curarg[0] != '\0')
    {
      int argInd = 0;

      if (curarg[0] == '"')
      {
        // there is a quote: no need to add extra quotes
      }
      else
      {
        while (curarg[argInd] != ' '
          && curarg[argInd] != '\0'
        && curarg[argInd] != '\n')
        {
          argInd++;
        }
      }
      if (curarg[0] != '"' && curarg[argInd] == ' ')
      {
        if (curCmdInd + strlen("\"\"") + strlen(curarg) < maxSize)
        {
          // no begining quote and white space inside => add quotes
          sprintf (&command[curCmdInd], "\"%s\"", curarg);
          curCmdInd = strlen (command);
        }
        else
        {
          overflow = TRUE;
        }
      }
      else
      {
        if (curCmdInd + strlen(curarg) < maxSize)
        {
          // no white space or quotes detected, keep the arg as is
          sprintf (&command[curCmdInd], "%s", curarg);
          curCmdInd = strlen (command);
        }
        else
        {
          overflow = TRUE;
        }
      }

    } else {
      if (curCmdInd + strlen("\"\"") < maxSize)
      {
        sprintf (&command[curCmdInd], "\"\"");
        curCmdInd = strlen (command);
      }
      else
      {
        overflow = TRUE;
      }
    }
  }

  if (overflow)
  {
    debugError("Failed to construct the full commandline because the buffer wasn't big enough.");
  }
  else
  {
    debug("The full commandline is '%s'.", command);
  }

  return !overflow;
} // getCommandLine

// ----------------------------------------------------
// Function called when we want to start the server.
// This function expects the following parameter to be passed:
// the directory of the server we want to start and the
// command line (and its argumets) that we want to execute (basically the java
// command that we want to start the server).  The main reasons
// to have the command line passed are:
// 1. Keep the native code as minimal as possible.
// 2. Allow the administrator some flexibility in the way the
// server is started by leaving most of the logic in the command-line.
//
// This approach makes things to be closer between what is proposed
// in windows and in UNIX systems.
//
// If the instance could be started the code will write the pid of the process
// of the server in file that can be used for instance to stop the server
// (see stop.c).
//
// Returns the pid of the process of the instance if it could be started and -1
// otherwise.
// ----------------------------------------------------
int start(const char* instanceDir, char* argv[])
{
  int returnValue;
  int childPid;

  char command[COMMAND_SIZE];

  if (getCommandLine(argv, command, COMMAND_SIZE))
  {
    childPid = spawn(command, TRUE);

    if (childPid > 0)
    {
      createPidFile(instanceDir, childPid);
      returnValue = childPid;
    }
    else
    {
      debugError("Couldn't start the child process because the spawn failed.");
      returnValue = -1;
    }
  }
  else
  {
    debugError("Couldn't start the child process because the full command line could not be constructed.");
    returnValue = -1;
  }

  return returnValue;
} // start


// ----------------------------------------------------
// Function called when we want to stop the server.
// This code is called by the stop-ds.bat batch file to stop the server
// in windows.
// This function expects just one parameter to be passed
// to the executable: the directory of the server we want
// to stop.
//
// If the instance could be stopped the pid file
// is removed.  This is done for security reasons: if we do
// not delete the pid file and the old pid of the process
// is used by a new process, when we call again this executable
// the new process will be killed.
// Note: even if the code in the class org.opends.server.core.DirectoryServer
// sets the pid file to be deleted on the exit of the process
// the file is not always deleted.
//
// Returns 0 if the instance could be stopped using the
// pid stored in a file of the server installation and
// -1 otherwise.
// ----------------------------------------------------
int stop(const char* instanceDir)
{
  int returnCode = -1;

  int childPid;

  debug("Attempting to stop the server running at root '%s'.", instanceDir);

  childPid = getPid(instanceDir);

  if (childPid != 0)
  {
    if (killProcess(childPid))
    {
      returnCode = 0;
      deletePidFile(instanceDir);
    }
  }
  else
  {
    debug("Could not stop the server running at root '%s' because the pid could not be located.", instanceDir);
  }

  return returnCode;
} // stop


// ----------------------------------------------------
// Function called when we want to launch simply a process without attaching
// it to any command prompt (the difference with start is basically that here
// we create no pid file).
// This code is called for instance by the statuspanel.bat batch file to launch
// the status panel on windows.

// The goal of these methods is:
// Be able to launch batch files with double-click and not having a
// prompt-window associated with it.
// Launch batch files from the prompt that generate a java process that does not
// block the prompt and that keeps running even if the prompt window is closed.
//
// This function expects the following parameter to be passed:
// the directory of the server we want to start and the
// command line that we want to execute (basically the java
// command that we want to display the status panel).  The main reasons
// to have the command line passed are:
// 1. Keep the native code as minimal as possible.
// 2. Allow the administrator some flexibility in the way the
// server is started by leaving most of the logic in the command-line.
//
// Returns the pid of the process associated with the command if it could be
// launched and -1 otherwise.
// ----------------------------------------------------
int launch(char* argv[])
{
  int returnValue;

  char command[COMMAND_SIZE];

  if (getCommandLine(argv, command, COMMAND_SIZE))
  {
    returnValue = spawn(command, TRUE);
    if (returnValue <= 0)
    {
      debugError("Failed to launch the child process '%s'.", command);
    }
    else
    {
      debug("Successfully launched the child process '%s'.", command);
    }
  }
  else
  {
    debugError("Couldn't launch the child process because the full command line could not be constructed.");
    returnValue = -1;
  }

  return returnValue;
} // launch

//----------------------------------------------------
// Function called when we want to launch a process and it to be run as
// administrator on Vista (the binary containing this function must have
// a manifest specifying that).
// Returns the exit code of the process associated with the command if it
// could be launched and -1 otherwise.
//----------------------------------------------------
int run(char* argv[])
{
  PROCESS_INFORMATION procInfo; // info on the new process
  BOOL createOk;
  DWORD exitCode;
  int returnValue = -1;
  int millisToWait = 30000;
  int waitedMillis = 0;

  char command[COMMAND_SIZE];

  if (getCommandLine(argv, command, COMMAND_SIZE))
  {
    createOk = createChildProcess((char*)command, TRUE, &procInfo);

    if(createOk)
    {
      GetExitCodeProcess(procInfo.hProcess, &exitCode);
      while (exitCode == STILL_ACTIVE)
      {
        GetExitCodeProcess(procInfo.hProcess, &exitCode);
        Sleep(500);
        waitedMillis += 500;
        if (waitedMillis > millisToWait)
        {
          break;
        }
      }
      returnValue = exitCode;
    }
  }
  return returnValue;
} // run

// ----------------------------------------------------
// main function called by the executable.  This code is
// called by the start-ds.bat, stop-ds.bat and statuspanel.bat batch files.
//
// The code assumes that the first passed argument is the subcommand to be
// executed and for start and stop the second argument the directory of the
// server.
// The rest of the arguments are the arguments specific to each subcommand (see
// the comments for the functions start, stop and launch).
// ----------------------------------------------------
int main(int argc, char* argv[])
{
  int returnCode;
  char* subcommand = NULL;
  char* instanceDir = NULL;
  int i;

  debug("main called.");
  for (i = 0; i < argc; i++) {
    debug("  argv[%d] = '%s'", i, argv[i]);
  }

  if (argc < 3) {
    char * msg =
      "Expected command line args of [subcommand], but got %d arguments.\n";
    debugError(msg, argc - 1);
    fprintf(stderr, msg, argc - 1);
	  return -1;
  }

  subcommand = argv[1];

  if (strcmp(subcommand, "start") == 0)
  {
    instanceDir = argv[2];
    argv += 3;
    returnCode = start(instanceDir, argv);
  }
  else if (strcmp(subcommand, "stop") == 0)
  {
    instanceDir = argv[2];
    argv += 3;
    returnCode = stop(instanceDir);
  }
  else if (strcmp(subcommand, "launch") == 0)
  {
    argv += 2;
    returnCode = launch(argv);
  }
  else if (strcmp(subcommand, "run") == 0)
  {
    argv += 2;
    returnCode = run(argv);
  }
  else
  {
    char * msg = "Unknown subcommand: [%s]\n";
    debugError(msg, subcommand);
    fprintf(stderr, msg, subcommand);
    returnCode = -1;
  }
  debug("main finished. Returning %d", returnCode);
  return returnCode;
}

