/*
|
* 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
|
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
|
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
|
* 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
|
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. 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
|
*
|
*
|
* Portions Copyright 2007 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, maxSize, "%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
|
|
// ----------------------------------------------------
|
// 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 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] [server directory], but got %d arguments.\n";
|
debugError(msg, argc - 1);
|
fprintf(stderr, msg, argc - 1);
|
return -1;
|
}
|
|
subcommand = argv[1];
|
instanceDir = argv[2];
|
|
argv += 3;
|
|
if (strcmp(subcommand, "start") == 0)
|
{
|
returnCode = start(instanceDir, argv);
|
}
|
else if (strcmp(subcommand, "stop") == 0)
|
{
|
returnCode = stop(instanceDir);
|
}
|
else if (strcmp(subcommand, "launch") == 0)
|
{
|
returnCode = launch(argv);
|
}
|
else
|
{
|
char * msg = "Unknown subcommand: [%s]\n";
|
debugError(msg, argc - 1);
|
fprintf(stderr, msg, subcommand);
|
returnCode = -1;
|
}
|
debug("main finished. Returning %d", returnCode);
|
return returnCode;
|
}
|