/********************************************************************\ 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 EvidenceServer::Config::Config(string Name): DimRpcInfo("ConfigRequest", NO_LINK), Name(Name) { // Initialise CurrentItem = string(); ConfigTimeStamp = 0; ThreadID = pthread_self(); // Subscribe to be informed on configuration file change Service = new DimInfo("Config/ModifyTime", NO_LINK, this); } // Destructor EvidenceServer::Config::~Config() { delete Service; } // Track last modification time of configuration file void EvidenceServer::Config::infoHandler() { This->Lock(); ConfigTimeStamp = getInfo()->getInt(); This->Unlock(); if (pthread_kill(ThreadID, This->ConfigSignal) != 0) { This->Message(WARN, "Could not send signal to main thread"); } } // Receive answer to remote procedure call void EvidenceServer::Config::rpcInfoHandler(){ This->Lock(); // Update map List[CurrentItem].Value = string(getString(), getSize()-1); List[CurrentItem].Time = ConfigTimeStamp; // Clear to allow new rpc call CurrentItem.clear(); This->Unlock(); } // Return configuration data if still up to date or empty string otherwise string EvidenceServer::Config::GetConfig(string Item, string Default) { string Result; // If up-to-date data in configuration list available, return this This->Lock(); if ((List.count(Item) > 0) && (List[Item].Time >= ConfigTimeStamp)) Result = List[Item].Value; This->Unlock(); if (!Result.empty()) return Result; // Blocking configuration request if in main thread if (pthread_self() == ThreadID) { DimRpcInfo Config((char *) "ConfigRequest", NO_LINK); Config.setData((char *) (Name + " " + Item).c_str()); // Check if successful if (!EvidenceServer::ServiceOK(&Config)) { if (Default.empty()) { This->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()) { This->Lock(); List[Item].Value = Result; List[Item].Time = ConfigTimeStamp; This->Unlock(); } } // Non-blocking configuration request from other threads if (pthread_self() != ThreadID) { This->Lock(); if (List.count(Item) > 0) { // New request possible only when answer to previous request received if (CurrentItem.empty()) { CurrentItem = Item; setData(((char *) (Name + " " + Item).c_str())); } // Return current value Result = List[Item].Value; This->Unlock(); } } return Result; } ////////////////////////// // EvidenceServer Class // ////////////////////////// int EvidenceServer::ConfigSignal = 0; EvidenceServer *EvidenceServer::This = NULL; // Constructor starts server with given name EvidenceServer::EvidenceServer(const char *ServerName): Name(ServerName) { // Initialize MessageService = NULL; MessageData = NULL; ExitRequest = false; This = this; // 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); // Configuration class (must be instantiate after signal handling installed) ConfClass = new class Config(Name); // 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__); // Start server start(ServerName); addExitHandler(this); } // Destructor: Free memory EvidenceServer::~EvidenceServer() { Message(INFO, "Server stopped"); delete ConfClass; delete MessageService; delete[] MessageData; int Ret; if ((Ret = pthread_mutex_destroy(&Mutex)) != 0) { Message(ERROR, "pthread_mutex_destroy() failed (%s)", strerror(Ret)); } } // 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, then delete old message if (MessageService != NULL) { MessageService->updateService(NewMsg, sizeof(struct Message)+strlen(NewMsg->Text)+1); } delete[] MessageData; MessageData = NewMsg; // Terminate if severity if FATAL if (Severity == FATAL) exit(EXIT_FAILURE); } // Set to central logging server with non-blocking command (may be used in DIM handler) void EvidenceServer::SendToLog(const char *Format, ...) { 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 Message(ERROR, "Could not create logging text in SendToLog(), vasprintf() failed"); } // 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 = ConfClass->GetConfig(Item, Default); if (Result.empty()) Message(FATAL, "Missing configuration data '%s'", Item.c_str()); return Result; } // Set signal emitted when configuraton file changes, signal handler calls ConfigChanged() void EvidenceServer::ActivateSignal(int Signal) { struct sigaction S; ConfigSignal = Signal; S.sa_handler = &SignalHandler; S.sa_flags = SA_RESTART; sigaction(Signal, &S, NULL); } // Locking and unlocking for list access // Signal blocked before locking to avoid dead-lock by calling GetConfig() from ConfigChanged(). void EvidenceServer::Lock() { int Ret; sigset_t Set; if (ConfigSignal != 0) { Ret = abs(sigemptyset(&Set)); Ret += abs(sigaddset(&Set, ConfigSignal)); Ret += abs(pthread_sigmask(SIG_BLOCK, &Set, NULL)); if (Ret != 0) Message(FATAL, "Signal masking failed in Lock()"); } if ((Ret = pthread_mutex_lock(&Mutex)) != 0) { Message(FATAL, "pthread_mutex_lock() failed in Lock() (%s)", strerror(Ret)); } } void EvidenceServer::Unlock() { int Ret; sigset_t Set; if ((Ret = pthread_mutex_unlock(&Mutex)) != 0) { Message(FATAL, "pthread_mutex_unlock() failed in Unlock() (%s)", strerror(Ret)); } if (ConfigSignal != 0) { Ret = abs(sigemptyset(&Set)); Ret += abs(sigaddset(&Set, ConfigSignal)); Ret += abs(pthread_sigmask(SIG_UNBLOCK, &Set, NULL)); if (Ret != 0) Message(FATAL, "Signal unmasking failed in Unlock()"); } } // ====== Static methods ====== // Signal handler (causes pause() and other syscalls to return) void EvidenceServer::SignalHandler(int Signal) { static bool Called = false; // If signal indicates configuration change, invoke call-back if (Signal == EvidenceServer::ConfigSignal) { This->ConfigChanged(); return; } // 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)); } // 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; }