/********************************************************************\ General code to start a server of the Evidence Control System - The server is started with the given name. - DIM exit and error handlers are implemented. - A Message service is published with severity encoding. It can be updated with the Message() method. The text will also be logged. - If the severity of a Message() call is FATAL, exit() will be called (with this severity, the call to Message() is guranteed not to return). - Configuration data can be requested by GetConfig() and non-blocking by GetConfigNB(). - If the configuration file changes a signal can be emitted which is caught by the standard signal handler. The handler invokes ConfigChanged() which can be redefined by the user application. The signal is delivered only to the main thread (where the constructor is executed) and thus blocking rpc can be made from it. The signal is set using ActivateSignal(). - Signal handlers to ignore common signals are installed. These signals will then cause pause() to return which can be used by the application to terminate gracefully. - The static method ToString() converts the contents of a DIM service into text - A terminate-handler is installed for catching unhandled C++ exceptions. Oliver Grimm \********************************************************************/ #include "Evidence.h" using namespace std; // // Internal configuration class of EvidenceServer // // Constructor: Subscribe to be informed on configuration file change EvidenceServer::Config::Config(): DimCommand((This->Name+"/ResetMessage").c_str(), (char *) ""), DimRpcInfo("ConfigRequest", NO_LINK) { CurrentItem = string(); ConfigTimeStamp = 0; Service = new DimInfo("Config/ModifyTime", NO_LINK, this); } // Destructor EvidenceServer::Config::~Config() { delete Service; } // Reset message and severity void EvidenceServer::Config::commandHandler() { This->Message(INFO, "Message reset by %s (ID %d)", getClientName(), getClientId()); } // Track last modification time of configuration file void EvidenceServer::Config::infoHandler() { pthread_t Thread; int Ret; if (!ServiceOK(DimInfo::getInfo())) return; This->Lock(); ConfigTimeStamp = getInfo()->getInt(); This->Unlock(); // Launch ConfigChanged() as detached thread if ((Ret = pthread_create(&Thread, NULL, (void * (*)(void *)) EvidenceServer::CallConfigChanged, (void *) NULL)) != 0) { This->Message(ERROR, "pthread_create() failed in EvidenceServer::Config::infoHandler() (%s)\n", strerror(Ret)); } else if ((Ret = pthread_detach(Thread)) != 0) { This->Message(ERROR, "pthread_detach() failed in EvidenceServer::Config::infoHandler() (%s)\n", strerror(Ret)); } } // Answer to remote procedure call: Update map void EvidenceServer::Config::rpcInfoHandler(){ This->Lock(); This->List[CurrentItem].Value = string(DimRpcInfo::getString(), DimRpcInfo::getSize()-1); This->List[CurrentItem].Time = ConfigTimeStamp; // Clear to allow new rpc call CurrentItem.clear(); This->Unlock(); } ////////////////////////// // EvidenceServer Class // ////////////////////////// // Initialise EvidenceServer *EvidenceServer::This = NULL; pthread_mutex_t EvidenceServer::Mutex; // Constructor EvidenceServer::EvidenceServer(string ServerName): Name(ServerName) { // Initialize MessageService = NULL; MessageData = NULL; ExitRequest = false; This = this; dis_disable_padding(); dic_disable_padding(); // Initialise mutex int Ret; pthread_mutexattr_t Attr; if ((Ret = pthread_mutexattr_init(&Attr)) != 0) { Message(FATAL, "pthread_mutex_init() failed in Evidence constructor (%s)", strerror(Ret)); } if ((Ret = pthread_mutexattr_settype(&Attr, PTHREAD_MUTEX_ERRORCHECK)) != 0) { Message(FATAL, "pthread_mutex_settype() failed in Evidence constructor (%s)", strerror(Ret)); } if ((Ret = pthread_mutex_init(&Mutex, &Attr)) != 0) { Message(FATAL, "pthread_mutex_init() failed in Evidence constructor (%s)", strerror(Ret)); } // Catch some signals signal(SIGQUIT, &SignalHandler); // CTRL-Backspace signal(SIGTERM, &SignalHandler); // Termination signal signal(SIGINT, &SignalHandler); // CTRL-C signal(SIGHUP, &SignalHandler); // Terminal closed // Catch C++ unhandled exceptions set_terminate(Terminate); // Message service and initial message MessageService = new DimService((Name+"/Message").c_str(), (char *) "I:1;C", NULL, 0); string Rev(EVIDENCE_REVISION); Message(INFO, "Server started (%s, compiled %s %s)", (Rev.substr(1, Rev.size()-3)).c_str(),__DATE__, __TIME__); // Configuration class ConfClass = new class Config(); // Start server start(ServerName.c_str()); addExitHandler(this); } // Destructor: Free memory EvidenceServer::~EvidenceServer() { Message(INFO, "Server stopped"); delete ConfClass; int Ret; if ((Ret = pthread_mutex_destroy(&Mutex)) != 0) { Message(ERROR, "pthread_mutex_destroy() failed (%s)", strerror(Ret)); } delete MessageService; delete[] MessageData; } // DIM exit handler // Note handler runs in DIM thread, thus exit() will call destructors of static objects in this thread void EvidenceServer::exitHandler(int Code) { Message(INFO, "Exit handler called (DIM exit code %d)", Code); exit(EXIT_SUCCESS); } // DIM error handler void EvidenceServer::errorHandler(int Severity, int Code, char *Text) { Message(ERROR, "%s (DIM error code %d, DIM severity %d)\n", Text, Code, Severity); } // Set server message (if Severity is FATAL, exit() will be invoked) void EvidenceServer::Message(int Severity, const char *Format, ...) { static char ErrorString[] = "vasprintf() failed in Message()"; char Buffer[10]; char *Text; // Assemble message from application va_list ArgumentPointer; va_start(ArgumentPointer, Format); if (vasprintf(&Text, Format, ArgumentPointer) == -1) { Text = ErrorString; Severity = ERROR; } va_end(ArgumentPointer); // Generate new Message structure and free text struct Message *NewMsg = (struct Message *) new char [sizeof(struct Message)+strlen(Text)+1]; NewMsg->Severity = Severity; strcpy(NewMsg->Text, Text); if (Text != ErrorString) free(Text); // Send message to console and log file const char *Desc; switch (Severity) { case INFO: Desc = "Info"; break; case WARN: Desc = "Warn"; break; case ERROR: Desc = "Error"; break; case FATAL: Desc = "Fatal"; break; default: snprintf(Buffer, sizeof(Buffer), "%d", Severity); Desc = Buffer; } printf("%s (%s): %s\n", MessageService->getName(), Desc, NewMsg->Text); SendToLog("%s (%s): %s", MessageService->getName(), Desc, NewMsg->Text); // Update DIM message service if (MessageService != NULL) { MessageService->updateService(NewMsg, sizeof(struct Message)+strlen(NewMsg->Text)+1); } // Delete old message Lock(); delete[] MessageData; MessageData = NewMsg; Unlock(); // Terminate if severity if FATAL if (Severity == FATAL) exit(EXIT_FAILURE); } // Send to central logging server with non-blocking command void EvidenceServer::SendToLog(const char *Format, ...) { static char ErrorString[] = "vasprintf() failed in SendToLog()"; char *Buffer; int Ret; // Evaluate variable argument list va_list ArgumentPointer; va_start(ArgumentPointer, Format); Ret = vasprintf(&Buffer, Format, ArgumentPointer); va_end(ArgumentPointer); // Send to logger if (Ret != -1) { DimClient::sendCommandNB("DColl/Log", Buffer); free (Buffer); } else DimClient::sendCommandNB("DColl/Log", ErrorString); } // Get configuration data // // Program terminates if data is missing and no default given. Actual configuration // request will be made only if config file has modification since last request. // If called from infoHandler(), a non-blocking request will be made string EvidenceServer::GetConfig(string Item, string Default) { string Result; // If up-to-date data in configuration list available, return this Lock(); if ((List.count(Item) > 0) && (List[Item].Time >= ConfClass->ConfigTimeStamp)) Result = List[Item].Value; Unlock(); if (!Result.empty()) return Result; if (inCallback() == 0) { // Blocking configuration request DimRpcInfo Config((char *) "ConfigRequest", NO_LINK); Config.setData((char *) (Name + " " + Item).c_str()); // Check if successful if (!EvidenceServer::ServiceOK(&Config)) { if (Default.empty()) { Message(FATAL, "Configuration server unreachable, can't retrieve '%s'", Item.c_str()); } else Result = Default; } else { if (Config.getSize() <= 1) Result = Default; else Result = string(Config.getString(), Config.getSize()-1); // Retrieve string safely } // Update configuration map if (!Result.empty()) { Lock(); List[Item].Value = Result; List[Item].Time = ConfClass->ConfigTimeStamp; Unlock(); } } else { // Non-blocking configuration request Lock(); // Make request if currently non is processed if (ConfClass->CurrentItem.empty()) { ConfClass->CurrentItem = Item; ConfClass->setData(((char *) (Name + " " + Item).c_str())); } if (List.count(Item) > 0) Result = List[Item].Value; else Result = Default; Unlock(); } // Terminate if no configuration information found if (Result.empty()) Message(FATAL, "Missing configuration data '%s'", Item.c_str()); return Result; } // Locking and unlocking functions. // Message() is not used to avoid infinite recursion void EvidenceServer::Lock() { int Ret; if ((Ret = pthread_mutex_lock(&EvidenceServer::Mutex)) != 0) { printf("pthread_mutex_lock() failed in Lock() (%s)", strerror(Ret)); SendToLog("pthread_mutex_lock() failed in Lock() (%s)", strerror(Ret)); exit(EXIT_FAILURE); } } void EvidenceServer::Unlock() { int Ret; if ((Ret = pthread_mutex_unlock(&EvidenceServer::Mutex)) != 0) { printf("pthread_mutex_unlock() failed in Unlock() (%s)", strerror(Ret)); SendToLog("pthread_mutex_unlock() failed in Unlock() (%s)", strerror(Ret)); exit(EXIT_FAILURE); } } // ====== Static methods ====== // Stub to call ConfigChanged() method of class as separate thread // Thread set is used to determine if blocking or non-blocking rpc is to be used void EvidenceServer::CallConfigChanged() { This->ConfigChanged(); } // Signal handler (causes pause() and other syscalls to return) void EvidenceServer::SignalHandler(int Signal) { static int Count = 0; // At first invocation just request exit if (++Count == 1) { This->ExitRequest = true; return; } // If invoked twice, call exit() if (Count == 2) { This->Message(WARN, "Signal handler called twice, invoking exit(). Will abort() on next signal without message. (signal %d)", Signal); exit(EXIT_FAILURE); } // If invoked more than twice, call abort() // Does not use Message() again, as this might be blocked abort(); } // C++ exception handler (derived from gcc __verbose_terminate_handler()) void EvidenceServer::Terminate() { ostringstream Msg; static bool Terminating = false; if (Terminating) { Msg << This->Name << ": Terminate() called recursively, calling abort()"; printf("%s\n", Msg.str().c_str()); This->SendToLog(Msg.str().c_str()); abort(); } Terminating = true; // Make sure there was an exception; terminate is also called for an // attempt to rethrow when there is no suitable exception. type_info *Type = abi::__cxa_current_exception_type(); if (Type != NULL) { int Status = -1; char *Demangled = NULL; Demangled = abi::__cxa_demangle(Type->name(), 0, 0, &Status); Msg << "Terminate() called after throwing an instance of '" << (Status==0 ? Demangled : Type->name()) << "'"; free(Demangled); // If exception derived from std::exception, more information. try { __throw_exception_again; } catch (exception &E) { Msg << " (what(): " << E.what() << ")"; } catch (...) { } } else Msg << "Terminate() called without an active exception"; This->Message(FATAL, Msg.str().c_str()); } // Translates DIM data safely to string (assures no invalid memory accesses are made) // Structure evaluation requires that no padding is used string EvidenceServer::ToString(char *Format, const void *Data, int Size) { ostringstream Text; vector Components; int ElementSize, N, Byte = 0; // Find component types Components = Tokenize(Format, ";"); for (unsigned int n=0; n 2) N = atoi(Components[n].c_str()+2); else N = numeric_limits::max(); // Determine length in bytes of elements switch (toupper(Components[n][0])) { case 'B': case 'V': case 'C': ElementSize = sizeof(char); break; case 'I': case 'L': ElementSize = sizeof(int); break; case 'S': ElementSize = sizeof(short); break; case 'F': ElementSize = sizeof(float); break; case 'D': ElementSize = sizeof(double); break; case 'X': ElementSize = sizeof(long long); break; default: return string(); } // Covert elements for (int i=0; i Size) return Text.str(); // Translate elements into text (handle string specially when format is 'C') switch (toupper(Components[n][0])) { case 'C': if (Components[n].size() == 1) { string String((char *) Data, Size-Byte); // Remove trailing '\0' if (!String.empty() && String[String.size()-1] == '\0') String.resize(String.size()-1); Text << String; return Text.str(); } Text << *((char *) Data); break; case 'B': case 'V': Text << *((char *) Data); break; case 'I': case 'L': Text << *((int *) Data); break; case 'S': Text << *((short *) Data); break; case 'F': Text << *((float *) Data); break; case 'D': Text << *((double *) Data); break; case 'X': Text << *((long long *) Data); break; } Byte += ElementSize; Data = (void *) ((char *) Data + ElementSize); // Space between entries Text << " "; } } return Text.str(); } // Checks if service contents indicates not available bool EvidenceServer::ServiceOK(DimInfo *Item) { return !((Item->getSize() == strlen(NO_LINK)+1) && (memcmp(Item->getData(), NO_LINK, Item->getSize()) == 0)); } bool EvidenceServer::ServiceOK(DimRpcInfo *Item) { return !((Item->getSize() == strlen(NO_LINK)+1) && (memcmp(Item->getData(), NO_LINK, Item->getSize()) == 0)); } bool EvidenceServer::ServiceOK(DimCurrentInfo *Item) { return !((Item->getSize() == strlen(NO_LINK)+1) && (memcmp(Item->getData(), NO_LINK, Item->getSize()) == 0)); } // Tokenize std::string using given delimeter list vector EvidenceServer::Tokenize(const string &String, const string &Delimiters) { vector Tokens; string::size_type Next, EndNext=0; while (EndNext != string::npos) { // Find next token Next = String.find_first_not_of(Delimiters, EndNext); EndNext = String.find_first_of(Delimiters, Next); // Stop if end of string reached if (Next == string::npos) break; // Add token to vector. Tokens.push_back(String.substr(Next, EndNext - Next)); } return Tokens; } /////////////////////////// // EvidenceHistory Class // /////////////////////////// // Organisaztion of history buffer // F | T S D | T S D | 0 0 ...... | T S D | T S D | 0 -1 // // F: Offset of oldest entry T: Time S: Size D: Data // F, T, S: int // Marker for history buffer const struct EvidenceHistory::Item EvidenceHistory::WrapMark = {0, -1, {}}; const struct EvidenceHistory::Item EvidenceHistory::EndMark = {0, 0, {}}; // Constructor EvidenceHistory::EvidenceHistory(std::string Name): Name(Name) { Buffer = NULL; } // Destructor EvidenceHistory::~EvidenceHistory() { delete[] Buffer; } // Requests service history (returns true if data received OK) bool EvidenceHistory::GetHistory() { DimRpcInfo R((char *) "ServiceHistory", NO_LINK); R.setData((char *) Name.c_str()); // Check if data OK if (!EvidenceServer::ServiceOK(&R)) return false; if (R.getSize() == 0) return false; // Copy data to buffer delete[] Buffer; BufferSize = R.getSize(); Buffer = new char [BufferSize]; memcpy(Buffer, R.getData(), BufferSize); DataStart = Buffer + strlen(Buffer) + 1; Rewind(); return true; } // Returns next item in history buffer const struct EvidenceHistory::Item *EvidenceHistory::Next() { if (Buffer == NULL) return NULL; // Check for wrap around if (memcmp(Pointer, &WrapMark, sizeof(WrapMark)) == 0) Pointer = (struct Item *) (DataStart + sizeof(int)); // Check if at end of ring buffer if (memcmp(Pointer, &EndMark, sizeof(EndMark)) == 0) return NULL; const struct Item *Ret = Pointer; Pointer = (struct Item *) ((char *) (Ret + 1) + Ret->Size); return Ret; } // Reset to start of buffer void EvidenceHistory::Rewind() { if (Buffer != NULL) Pointer = (struct Item *) (DataStart + (*(int *) DataStart)); } // Return DIM format string of service (NULL if no data) char *EvidenceHistory::GetFormat() { return Buffer; }