/********************************************************************\ 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, June 2010 \********************************************************************/ #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; 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(); } // Request configuration data possible only when answer to previous request received void EvidenceServer::Config::RequestNB(string Item) { This->Lock(); if (CurrentItem.empty()) { CurrentItem = Item; setData(((char *) Item.c_str())); } This->Unlock(); } ////////////////////////// // EvidenceServer Class // ////////////////////////// // Initialise EvidenceServer *EvidenceServer::This = NULL; pthread_mutex_t EvidenceServer::Mutex; set EvidenceServer::Threads; // Constructor EvidenceServer::EvidenceServer(string ServerName): Name(ServerName) { // Initialize MessageService = NULL; MessageData = NULL; ExitRequest = false; This = this; Threads.insert(pthread_self()); // Initialise mutex int Ret; pthread_mutexattr_t Attr; if ((Ret = pthread_mutexattr_settype(&Attr, PTHREAD_MUTEX_ERRORCHECK)) != 0) { Message(FATAL, "pthread_mutex_settype() failed (%s)", strerror(Ret)); } if ((Ret = pthread_mutex_init(&Mutex, &Attr)) != 0) { Message(FATAL, "pthread_mutex_init() failed (%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 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(MessageType Severity, const char *Format, ...) { static const char* StateString[] = {"Info", "Warn", "Error", "Fatal"}; static char ErrorString[] = "vasprintf() failed in Message()"; 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 printf("%s (%s): %s\n", MessageService->getName(), StateString[Severity], NewMsg->Text); SendToLog("%s (%s): %s", MessageService->getName(), StateString[Severity], 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; bool Blocking = false; // 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; if (Threads.count(pthread_self()) != 0) Blocking = true; Unlock(); if (!Result.empty()) return Result; // Blocking configuration request if (Blocking) { 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(); } } // Non-blocking configuration request if (!Blocking) { Lock(); if (List.count(Item) > 0) { ConfClass->RequestNB(Name + " " + Item); Result = List[Item].Value; } 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() { EvidenceServer::Lock(); EvidenceServer::Threads.insert(pthread_self()); EvidenceServer::Unlock(); This->ConfigChanged(); EvidenceServer::Lock(); EvidenceServer::Threads.erase(pthread_self()); EvidenceServer::Unlock(); } // Signal handler (causes pause() and other syscalls to return) void EvidenceServer::SignalHandler(int Signal) { static bool Called = false; // At first invocation just request exit if (!Called) { Called = true; This->ExitRequest = true; return; } // If invoked twice, call exit() This->Message(WARN, "Signal handler called again, invoking exit() (signal %d)", Signal); exit(EXIT_FAILURE); } // 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) string EvidenceServer::ToString(char *Format, void *Data, int Size) { ostringstream Text; // 'Message' service format handled here if (strcmp(Format, "I:1;C") == 0 && Size >= (int) sizeof(struct Message)) { struct Message *Msg = (struct Message *) Data; // Safely extract string and limit to length of C string (padding might make it longer) string MsgText(Msg->Text, Size-sizeof(struct Message)); Text << Msg->Severity << " " << MsgText.erase(strlen(MsgText.c_str())); return Text.str(); } // Structure: print hex representation if (strlen(Format) != 1) { for (int i=0; i 0 && *((char *) Data+Size-1)=='\0') { return string((char *) Data); } // Number array int ElementSize; switch (*Format) { 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(); } for (int i=0; igetSize() == 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; }