CASA/CASA-auth-token/server/AuthTokenValidate/Svc/linux/server.cpp
Juan Carlos Luciani f90f3d91bf Reverted the forked process model changes after finding out that
they needed to be more involved (Each child process needed to
initialize the JVM) and because it introduced a whole set of
new problems that were not acceptible. Instead, now we will support
an optional single-threaded process model that can be invoked to
deal with JVMs that have trouble executing when invoked from a
thread.
2006-11-15 05:06:54 +00:00

981 lines
28 KiB
C++

/***********************************************************************
*
* 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: Juan Carlos Luciani <jluciani@novell.com>
*
***********************************************************************/
//===[ Include files ]=====================================================
#include "internal.h"
#include <jni.h>
//===[ External data ]=====================================================
//===[ External prototypes ]===============================================
//===[ Manifest constants ]================================================
#define MAXFD 64
#define DOMAIN_SOCKET_FILE_NAME "/var/lib/CASA/authtoken/validate/socket"
//===[ Type definitions ]==================================================
//===[ Function prototypes ]===============================================
void*
WorkerThread(void*);
//===[ Global variables ]==================================================
// Usage string
char usage[] = "\nCasaAuthtokenValidateD: usage: [-p ListenPort] [-b BeginThreads] [-g GrowThreads] [-m MaxThreads] [-D DebugLevel] [-d] [-s]\n";
// Worker thread pool configuration parameters
int beginThreads = 5;
int growThreads = 5;
int maxThreads = 4096;
int minWaitingThreads = beginThreads;
int maxWaitingThreads = beginThreads * 4;
// Worker thread pool operating parameters
double numThreads = 0;
double numBusyThreads = 0;
double numPerishingThreads = 0;
// Listen Port Number
//int listenPortNumber = 5000;
int listenPortNumber = 0;
// Parameter indicating whether or not the server needs to run
// as a daemon.
bool daemonize = false;
// Name to use for logging purposes
char appName[] = "CasaAuthtokenValidateD";
// Debug Level
int DebugLevel = 0;
bool UseSyslog = false;
// Variables for daemon auto-restart after crash feature
static bool autoRestartAfterCrash = true;
// Synchronization variables
pthread_mutex_t interlockedMutex;
pthread_mutex_t serverMutex;
pthread_cond_t serverCondition;
// Operating parameters
bool singleThreaded = false;
bool terminating = false;
// Java parameters
JavaVM *g_jvm = NULL;
JNIEnv *g_env = NULL;
char classpath[] = "-Djava.class.path=/usr/share/java/CASA/authtoken/CasaAuthToken.jar:/usr/share/java/CASA/authtoken/external/axis.jar:/usr/share/java/CASA/authtoken/external/axis-ant.jar:/usr/share/java/CASA/authtoken/external/commons-discovery-0.2.jar:/usr/share/java/CASA/authtoken/external/commons-logging-1.0.4.jar:/usr/share/java/CASA/authtoken/external/jaxrpc.jar:/usr/share/java/CASA/authtoken/external/log4j-1.2.8.jar:/usr/share/java/CASA/authtoken/external/saaj.jar:/usr/share/java/CASA/authtoken/external/wsdl4j-1.5.1.jar:/usr/share/java/CASA/authtoken/external/wss4j-1.5.0.jar:/usr/share/java/CASA/authtoken/external/xalan.jar:/usr/share/java/CASA/authtoken/external/xercesImpl.jar:/usr/share/java/CASA/authtoken/external/xml-apis.jar:/usr/share/java/CASA/authtoken/external/xmlsec-1.2.1.jar:/usr/share/java/CASA/authtoken/external:/etc/CASA/authtoken/keys/client";
// Java AuthenticationToken Class and method name
//char authTokenClassName[] = "jtest";
//char authTokenClassValidateMethodName[] = "test4";
char authTokenClassName[] = "com/novell/casa/authtoksvc/AuthToken";
char authTokenClassValidateMethodName[] = "validate";
//++=======================================================================
void
ServiceRequests(void)
//
// Arguments:
//
// Returns:
//
// Abstract:
//
// Notes:
//
// L2
//=======================================================================--
{
DbgTrace(1, "ServiceRequests- Start\n", 0);
// We are now attached to the JVM, find the helper class that
// we need.
jclass helperClass = g_env->FindClass(authTokenClassName);
if (helperClass)
{
// Helper class found, now get the id of the method that we invoke
jmethodID mId = g_env->GetStaticMethodID(helperClass,
authTokenClassValidateMethodName,
"(Ljava/lang/String;)Ljava/lang/String;");
if (mId)
{
// Loop until told to terminate
while (!terminating)
{
// Get a request that needs servicing
int32_t requestId = IpcServerGetRequest();
if (requestId != 0)
{
// We got a request that needs servicing, now get the
// data associated with it.
char *pReqData;
int dataLen = IpcServerGetRequestData(requestId, &pReqData);
if (dataLen != 0)
{
// Lets push the jvm local frame to allow us to clean up our local
// references later.
g_env->PushLocalFrame(10);
// Encapsulate the request data into a string object
jstring inString = g_env->NewStringUTF(pReqData);
if (inString)
{
// Invoke our helper method
jstring outString = (jstring) g_env->CallStaticObjectMethod(helperClass, mId, inString);
// Check if an excption occurred
if (g_env->ExceptionCheck() == JNI_TRUE)
{
// There is a pending exception, display the info which in turn clears it.
g_env->ExceptionDescribe();
IpcServerAbortRequest(requestId);
}
else
{
if (outString)
{
// The helper method succeded, complete the request
// with the data returned.
const char *pOutChars = g_env->GetStringUTFChars(outString, NULL);
if (pOutChars)
{
IpcServerCompleteRequest(requestId, (char*) pOutChars);
g_env->ReleaseStringUTFChars(outString, pOutChars);
}
else
{
DbgTrace(0, "ServiceRequests- Unable to get UTF characters\n", 0);
IpcServerAbortRequest(requestId);
}
}
else
{
// The helper method failed, just abort the request.
IpcServerAbortRequest(requestId);
}
}
}
else
{
DbgTrace(0, "ServiceRequests- UTF String allocation failure\n", 0);
IpcServerAbortRequest(requestId);
}
// Pop the jvm local frame to clean up our local references
g_env->PopLocalFrame(NULL);
}
else
{
DbgTrace(0, "ServiceRequests- Error obtaining Request data\n", 0);
IpcServerAbortRequest(requestId);
}
}
else
{
// No need to service requests any longer
break;
}
}
}
else
{
DbgTrace(0, "ServiceRequests- Failed to get method id\n", 0);
}
}
else
{
DbgTrace(0, "ServiceRequests- Failed to find helper class\n", 0);
}
DbgTrace(1, "ServiceRequests- End\n", 0);
} /*-- ServiceRequests() --*/
//++=======================================================================
void
GrowWorkerThreadPool(int growNumber)
//
// Arguments:
//
// Returns:
//
// Abstract:
//
// Notes: The serverMutex needs to be held when calling this
// procedure.
//
// L2
//=======================================================================--
{
DbgTrace(1, "GrowWorkerThreadPool- Start\n", 0);
for (int i = 0; i < growNumber; i++)
{
int threadCreateStatus;
pthread_t thread;
if ((threadCreateStatus = pthread_create(&thread,
NULL,
(void*(*)(void*))WorkerThread,
NULL) == 0))
{
// Worker thread created
numThreads ++;
}
else
{
DbgTrace(0, "GrowWorkerThreadPool- Thread creation failed, status = %0d\n", threadCreateStatus);
}
}
// Let our server know if we ended up with no worker threads
if (numThreads == 0)
pthread_cond_signal(&serverCondition);
DbgTrace(1, "GrowWorkerThreadPool- End\n", 0);
} /*-- GrowWorkerThreadPool() --*/
//++=======================================================================
void
WorkerThreadBusy(void)
//
// Arguments:
//
// Returns:
//
// Abstract:
//
// Notes:
//
// L2
//=======================================================================--
{
DbgTrace(1, "WorkerThreadBusy- Start\n", 0);
// Acquire our mutex
pthread_mutex_lock(&serverMutex);
// Increment the numBusyThread count and grow the number of worker threads
// if necessary.
numBusyThreads ++;
if ((numThreads - numBusyThreads) < minWaitingThreads)
GrowWorkerThreadPool(growThreads);
// Release our mutex
pthread_mutex_unlock(&serverMutex);
DbgTrace(1, "WorkerThreadBusy- End\n", 0);
} /*-- WorkerThreadBusy() --*/
//++=======================================================================
bool
WorkerThreadWaiting(void)
//
// Arguments:
//
// Returns:
//
// Abstract:
//
// Notes:
//
// L2
//=======================================================================--
{
bool retValue;
DbgTrace(1, "WorkerThreadWaiting- Start\n", 0);
// Acquire our mutex
pthread_mutex_lock(&serverMutex);
// Decrement the numBusyThread count
numBusyThreads --;
// Check if we have too many idle workers
if ((numThreads - numBusyThreads - numPerishingThreads) > maxWaitingThreads
&& numThreads > beginThreads)
{
// We want to let this worker perish
numPerishingThreads ++;
retValue = true;
}
else
retValue = false;
// Release our mutex
pthread_mutex_unlock(&serverMutex);
DbgTrace(1, "WorkerThreadWaiting- End, retValue = %X\n", retValue);
return retValue;
} /*-- WorkerThreadWaiting() --*/
//++=======================================================================
void*
WorkerThread(void*)
//
// Arguments:
//
// Returns:
//
// Abstract:
//
// Notes:
//
// L2
//=======================================================================--
{
bool perishingThread = false;
DbgTrace(1, "WorkerThread- Start\n", 0);
// Set the thread in the detached state so that it is cleaned up when it exits
pthread_detach(pthread_self());
// Attach the thread to the JVM
JNIEnv *env;
JavaVMAttachArgs attachArgs = {0};
attachArgs.version = JNI_VERSION_1_4;
if (g_jvm->AttachCurrentThread((void**) &env, &attachArgs) >= 0)
{
// We are now attached to the JVM, find the helper class that
// we need.
jclass helperClass = env->FindClass(authTokenClassName);
if (helperClass)
{
// Helper class found, now get the id of the method that we invoke
jmethodID mId = env->GetStaticMethodID(helperClass,
authTokenClassValidateMethodName,
"(Ljava/lang/String;)Ljava/lang/String;");
if (mId)
{
// Loop until told to terminate
while (!terminating)
{
// Get a request that needs servicing
int32_t requestId = IpcServerGetRequest();
if (requestId != 0)
{
// We got a request that needs servicing, now get the
// data associated with it.
char *pReqData;
int dataLen = IpcServerGetRequestData(requestId, &pReqData);
if (dataLen != 0)
{
// Indicate that we are now busy
WorkerThreadBusy();
// Lets push the jvm local frame to allow us to clean up our local
// references later.
env->PushLocalFrame(10);
// Encapsulate the request data into a string object
jstring inString = env->NewStringUTF(pReqData);
if (inString)
{
// Invoke our helper method
jstring outString = (jstring) env->CallStaticObjectMethod(helperClass, mId, inString);
// Check if an excption occurred
if (env->ExceptionCheck() == JNI_TRUE)
{
// There is a pending exception, display the info which in turn clears it.
env->ExceptionDescribe();
IpcServerAbortRequest(requestId);
}
else
{
if (outString)
{
// The helper method succeded, complete the request
// with the data returned.
const char *pOutChars = env->GetStringUTFChars(outString, NULL);
if (pOutChars)
{
IpcServerCompleteRequest(requestId, (char*) pOutChars);
env->ReleaseStringUTFChars(outString, pOutChars);
}
else
{
DbgTrace(0, "WorkerThread- Unable to get UTF characters\n", 0);
IpcServerAbortRequest(requestId);
}
}
else
{
// The helper method failed, just abort the request.
IpcServerAbortRequest(requestId);
}
}
}
else
{
DbgTrace(0, "WorkerThread- UTF String allocation failure\n", 0);
IpcServerAbortRequest(requestId);
}
// Pop the jvm local frame to clean up our local references
env->PopLocalFrame(NULL);
// Indicate that we are no longer busy and get indication of
// whether or not we should continue to try to process requests.
if (WorkerThreadWaiting() == true)
{
DbgTrace(1, "WorkerThread- Requested to terminate\n", 0);
// Remember that we are a perishing thread so that we can reduce the
// count as we exit.
perishingThread = true;
break;
}
}
else
{
DbgTrace(0, "WorkerThread- Error obtaining Request data\n", 0);
IpcServerAbortRequest(requestId);
}
}
else
{
// No need to service requests any longer
break;
}
}
}
else
{
DbgTrace(0, "WorkerThread- Failed to get method id\n", 0);
}
}
else
{
DbgTrace(0, "WorkerThread- Failed to find helper class\n", 0);
}
// Detach from the JVM
g_jvm->DetachCurrentThread();
}
else
{
DbgTrace(0, "WorkerThread- Failed to attach to JVM\n", 0);
}
// Decrement the number of worker threads and signal our main thread
// to terminate itself if we are the last worker thread.
pthread_mutex_lock(&serverMutex);
if (perishingThread)
numPerishingThreads --;
numThreads --;
if (numThreads == 0)
pthread_cond_signal(&serverCondition);
pthread_mutex_unlock(&serverMutex);
DbgTrace(1, "WorkerThread- End\n", 0);
// Exit
pthread_exit(NULL);
return 0; // never-reached!
} /*-- WorkerThread() --*/
//++=======================================================================
void
SigTermHandler(
int signum)
//
// Arguments:
//
// Returns:
//
// Abstract:
//
// Notes:
//
// L2
//=======================================================================--
{
DbgTrace(1, "SigTermHandler- Start\n", 0);
// Indicate that we are terminating
terminating = true;
// Shutdown the IPC Server
IpcServerShutdown();
DbgTrace(1, "SigTermHandler- End\n", 0);
} /*-- SigTermHandler() --*/
//++=======================================================================
int
InitJavaInvoke(void)
//
// Arguments:
//
// Returns:
//
// Abstract:
//
// Notes:
//
// L2
//=======================================================================--
{
int retStatus = -1;
DbgTrace(1, "InitJavaInvoke- Start\n", 0);
JavaVMOption options[6];
options[0].optionString = classpath;
options[1].optionString = "-Dorg.xml.sax.driver=org.apache.xerces.parsers.SAXParser";
options[2].optionString = "-Djavax.xml.parsers.DocumentBuilderFactory=org.apache.xerces.jaxp.DocumentBuilderFactoryImpl";
options[3].optionString = "-Djavax.xml.parsers.SAXParserFactory=org.apache.xerces.jaxp.SAXParserFactoryImpl";
//options[4].optionString = "-Xcheck:jni";
//options[5].optionString = "-Djaxp.debug=1";
JavaVMInitArgs vm_args;
vm_args.version = JNI_VERSION_1_4;
vm_args.options = options;
vm_args.nOptions = 4;
vm_args.ignoreUnrecognized = true;
if (JNI_CreateJavaVM(&g_jvm, (void**)&g_env, &vm_args) >= 0)
{
// Success
retStatus = 0;
}
else
{
DbgTrace(0, "InitJavaInvoke- Error creating Java VM\n", 0);
}
DbgTrace(1, "InitJavaInvoke- End, retStatus = %08X\n", retStatus);
return retStatus;
} /*-- InitJavaInvoke() --*/
//++=======================================================================
void
UnInitJavaInvoke(void)
//
// Arguments:
//
// Returns:
//
// Abstract:
//
// Notes:
//
// L2
//=======================================================================--
{
DbgTrace(1, "UnInitJavaInvoke- Start\n", 0);
// Destroy the jvm
g_jvm->DestroyJavaVM();
g_jvm = NULL;
g_env = NULL;
DbgTrace(1, "UnInitJavaInvoke- End\n", 0);
} /*-- UnInitJavaInvoke() --*/
//++=======================================================================
void
DaemonInit(
const char *pname)
//
// Arguments:
//
// Returns:
//
// Abstract:
//
// Notes: Copy of daemon_init() in Richard Stevens Unix Network
// Programming Book.
//
// L2
//=======================================================================--
{
pid_t pid;
char *pNoAutoRestartEnvvar;
DbgTrace(1, "DaemonInit- Start\n", 0);
// Determine if we need to disable the auto-restart after crash feature
if ((pNoAutoRestartEnvvar = getenv("CASA_NO_AUTORESTART_AFTER_CRASH")) != NULL
&& strcmp(pNoAutoRestartEnvvar, "0") != 0)
{
DbgTrace(1, "DaemonInit- Disabling daemon auto-restart after crash feature\n", 0);
autoRestartAfterCrash = false;
}
// Fork to run in the background, check for error.
if ((pid = fork()) == -1)
{
DbgTrace(0, "DaemonInit- Fork error = %d\n", errno);
exit(0);
}
else if (pid != 0)
{
// The fork succeeded and we are the parent process, terminate
// ourselves.
exit(0);
}
/* 1st child continues */
// Become the session leader and set to ignore SIGHUP
setsid();
signal(SIGHUP, SIG_IGN);
// Fork again to guarantee that the daemon can not acquire a
// controlling terminal.
if ((pid = fork()) == -1)
{
DbgTrace(0, "DaemonInit- Fork error = %d\n", errno);
exit(0);
}
else if (pid != 0)
{
// The fork succeeded and we are the parent process, terminate
// ourselves.
exit(0);
}
/* 2nd child continues */
// Close any open descriptors
for (int i = 0; i < MAXFD; i++)
close(i);
// Spawn a worker
if ((pid = fork()) == -1)
{
DbgTrace(0, "DaemonInit- Fork error = %d\n", errno);
exit(0);
}
else if (pid == 0)
{
// The fork succeeded and we are the worker, continue.
}
else
{
// We are the parent of the server, check if we must execute the auto-restart
// server after crash logic.
if (autoRestartAfterCrash)
{
// Execute auto-restart server after crash logic
while (1)
{
int childExitStatus;
// Wait for children to exit
pid = wait(&childExitStatus);
if (pid != -1)
{
// Fork worker
if ((pid = fork()) == -1)
{
DbgTrace(0, "DaemonInit- Fork error = %d\n", errno);
exit(0);
}
else if (pid == 0)
{
// The fork succeeded and we are the server, exit the loop
// to start.
goto childContinue;
}
// We are the parent process, continue to watch for a terminated child process.
syslog(LOG_USER | LOG_INFO, "CasaAuthtokenValidateD: Worker re-started after it terminated unexpectedly");
sleep(1); // To keep from consuming too many cycles
}
else
{
// Check if we must exit the loop
if (errno != EINTR)
break;
}
}
}
// Terminate ourselves.
exit(0);
}
childContinue:
// Set flag to inform DbgTrace macros to use Syslog
UseSyslog = true;
// Change the working directory
chdir("/var/lib/CASA/authtoken/validate");
// Clear our file mode creation mask
umask(0);
// Get ready to log
openlog(appName, LOG_CONS | LOG_NOWAIT | LOG_ODELAY| LOG_PID, LOG_USER);
if (DebugLevel == 0)
setlogmask(LOG_UPTO(LOG_INFO));
else
setlogmask(LOG_UPTO(LOG_DEBUG));
DbgTrace(1, "DaemonInit- End\n", 0);
} /*-- DaemonInit() --*/
//++=======================================================================
int
main(
int argc,
char* argv[])
//
// Arguments:
//
// Returns:
//
// Abstract:
//
// Notes:
//
// L2
//=======================================================================--
{
int optionsSpecified = 0;
bool doneScanning = false;
bool invalidOption = false;
int option;
//printf("**** AuthTokenValidate Daemon ****\n");
// Scan through the options specified
while (!doneScanning)
{
opterr = 0;
option = getopt(argc, argv, "m:p:b:g:D:ds");
// Proceed based on the result
switch (option)
{
case 'p':
// Port number option, record location of
// argument.
listenPortNumber = atoi(optarg);
optionsSpecified ++;
break;
case 'b':
// Begin threads option, override the default parameter
// with the value of the option.
beginThreads = atoi(optarg);
optionsSpecified ++;
break;
case 'g':
// Grow threads option, override the default parameter
// with the value of the option.
growThreads = atoi(optarg);
optionsSpecified ++;
break;
case 'm':
// Max threads option, override the default parameter
// with the value of the option.
maxThreads = atoi(optarg);
optionsSpecified ++;
break;
case 'd':
// Run as daemon option
daemonize = true;
optionsSpecified ++;
break;
case 's':
// Run single threaded
singleThreaded = true;
optionsSpecified ++;
break;
case 'D':
// Set the debug level
DebugLevel = atoi(optarg);
optionsSpecified++;
break;
case '?':
// Invalid option detected
doneScanning = true;
invalidOption = true;
break;
default:
// Done scanning
doneScanning = true;
break;
}
}
// Do some sanity checking
if (!invalidOption
&& beginThreads > 0
&& maxThreads > (growThreads+beginThreads)
&& beginThreads <= maxThreads)
{
// The server is ready to start, check if we must
// run it as a daemon.
if (daemonize)
DaemonInit(argv[0]);
// Set a handler for SIGTERM
signal(SIGTERM, SigTermHandler);
// Initialize our mutexes
pthread_mutex_init(&interlockedMutex, NULL);
pthread_mutex_init(&serverMutex, NULL);
// Initialize the JVM
if (InitJavaInvoke() == 0)
{
// Initialize the condition that we will use to wait
// for the exit of all of our worker threads.
if (pthread_cond_init(&serverCondition, NULL) == 0)
{
// Initialize the IPC Server
if (IpcServerInit(appName,
DebugLevel,
UseSyslog) == 0)
{
// Now setup the appropriate listen address
int setAddressResult;
if (listenPortNumber == 0)
setAddressResult = IpcServerSetUnAddress(DOMAIN_SOCKET_FILE_NAME);
else
setAddressResult = IpcServerSetInAddress(listenPortNumber);
if (setAddressResult == 0)
{
// Now start the IPC server
if (IpcServerStart() == 0)
{
// Proceed according to run model configured
if (singleThreaded)
{
// Runninf in single-threaded model
ServiceRequests();
}
else
{
// Running in multi-threaded model
//
// Acquire our mutex
pthread_mutex_lock(&serverMutex);
// Start worker threads
GrowWorkerThreadPool(beginThreads);
// Wait for the worker threads to terminate
pthread_cond_wait(&serverCondition, &serverMutex);
// Release our mutex
pthread_mutex_unlock(&serverMutex);
}
DbgTrace(0, "main- Exiting, numThreads = %d\n", numThreads);
}
}
else
{
DbgTrace(0, "main- Setting of listen address failed\n", 0);
}
}
else
{
DbgTrace(0, "main- Initialization of Ipc server failed\n", 0);
}
}
else
{
DbgTrace(0, "main- Condition initialization failed\n", 0);
}
// Un-initialize JVM
UnInitJavaInvoke();
}
else
{
DbgTrace(0, "main- JVM initialization failed\n", 0);
}
}
else
{
// Invalid option detected or the user failed to
// specify the listening port number.
printf(usage, argv[0]);
}
return 0;
} /*-- main() --*/
//=========================================================================
//=========================================================================