/*********************************************************************** * * 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 * ***********************************************************************/ //===[ Include files ]===================================================== #include "internal.h" #include //===[ 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]\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 XSrv needs to run // as a daemon. bool daemonize = false; // Name to use for logging purposes char appName[] = "CasaAuthtokenValidateD"; // Debug Level int DebugLevel = 3; 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 terminating = false; // Java parameters JavaVM *g_jvm = NULL; JNIEnv *g_env = NULL; // Java AuthenticationToken Class and method name //char authTokenClassName[] = "jtest"; //char authTokenClassValidateMethodName[] = "test4"; char authTokenClassName[] = "com.novell.casa.authtoksvc.AuthToken"; char authTokenClassValidateMethodName[] = "validate"; //++======================================================================= 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; if (g_jvm->AttachCurrentThread((void**) &env, NULL) >= 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); 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[1]; //options[0].optionString = "-Djava.class.path=."; JavaVMOption options[1]; options[0].optionString = "-Djava.class.path=/usr/share/java:/etc/CASA/authtoken"; JavaVMInitArgs vm_args; vm_args.version = JNI_VERSION_1_4; vm_args.options = options; vm_args.nOptions = 1; 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:d"); // 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 '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) { // 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() --*/ //========================================================================= //=========================================================================