/********************************************************************\ Configuration server for the Evidence Control System - The name of a configuration file can be given as command line argument - If a configuration file change is detected through inotify, the service "Config/ModifyTime" contains the last modification UNIX time. - The contents of the configuration file is available through Config/ConfigData. - The current parser removes all tabs, multiple, leading and trailing spaces, and concatenates lines ending with '+'. A mutex is used for preventing concurrent access to the configuration data from the methods rpcHandler() and ConfigChanged(). Oliver Grimm, April 2010 \********************************************************************/ #define DEFAULT_CONFIG "../config/Evidence.conf" #define SERVER_NAME "Config" #include "Evidence.h" #include #include #include #include using namespace std; // // Class derived from DimRpc // class EvidenceConfig: public DimRpc, public EvidenceServer { private: struct Item { string Name; string Data; }; vector List; FILE *File; char *FileContent; DimService *ConfigModified; DimService *ConfigContent; pthread_mutex_t Mutex; void rpcHandler(); void AddItem(string, string, string); string RemoveSpaces(string &); public: EvidenceConfig(const char *); ~EvidenceConfig(); void ConfigChanged(); }; // Constructor EvidenceConfig::EvidenceConfig(const char *Filename): DimRpc("ConfigRequest", "C", "C"), EvidenceServer(SERVER_NAME) { ConfigModified = NULL; // Will be allocated in ConfigChanged() ConfigContent = NULL; FileContent = NULL; // Initialise mutex (errno is not set by pthread_mutex_init()) if (pthread_mutex_init(&Mutex, NULL) != 0) { Message(FATAL, "pthread_mutex_init() failed"); } // Open configuration file if ((File = fopen(Filename, "r")) == NULL) { Message(FATAL, "Could not open configuration file '%s' (%s)", Filename, strerror(errno)); } // Disable buffering, so file modifications are immediately seen if (setvbuf(File, NULL, _IONBF, 0) != 0) { Message(WARN, "Error setting configuration file '%s' to unbuffered mode", Filename); } // Create DIM services ConfigChanged(); } // Destructor EvidenceConfig::~EvidenceConfig() { if (File != NULL && fclose(File) != 0) Message(ERROR, "Error closing configuration file (%s)", strerror(errno)); delete ConfigModified; delete ConfigContent; delete[] FileContent; if (pthread_mutex_destroy(&Mutex) != 0) Message(ERROR, "pthread_mutex_destroy() failed"); } // Implementation of response to configuration request void EvidenceConfig::rpcHandler() { string Response; // Lock because ConfigChange() might access concurrently if (pthread_mutex_lock(&Mutex) != 0) Message(ERROR, "pthread_mutex_lock() failed in rpcHandler()"); // Search for config data in list (default response is empty string) for (int i=0; iupdateService(); // Read new configuration file (enfore \0 termination) char *NewContent = new char [Stat.st_size+1]; rewind(File); if (fread(NewContent, sizeof(char), Stat.st_size, File) != Stat.st_size) { Message(FATAL, "Could not read configuration file"); } NewContent[Stat.st_size] = '\0'; // Create/update DIM service that contains the current configuration file if (ConfigContent == NULL )ConfigContent = new DimService(SERVER_NAME"/ConfigData", NewContent); else ConfigContent->updateService(NewContent); delete[] FileContent; FileContent = NewContent; // // Part B: Interpret configuration list // stringstream In(FileContent), Out; string Section, Item, Line, Data; // First clean up and concatenate lines while (getline(In, Line).good()) { // Remove comments if (Line.find('#') != string::npos) Line.erase(Line.find('#')); // Replace all tabs by spaces while (Line.find("\t") != string::npos) Line[Line.find("\t")] = ' '; // Remove empty lines if (RemoveSpaces(Line).empty()) continue; // Concatenate if line ends with '\' if (Line[Line.size()-1] != '\\') Out << Line << endl; else Out << Line.erase(Line.size()-1); }; In.str(Out.str()); In.clear(); // Interpret data while (getline(In, Line).good()) { // Check if current line is section heading (contains only [xxx]) if (Line.find('[')==0 && Line.find(']')==Line.size()-1) { // Add previous item to list (if any) AddItem(Section, Item, Data); Item.clear(); Data.clear(); // Extract new section name Section = Line.substr(1, Line.size()-2); continue; } // Check if current line contains equal sign (defines new item name) if((Line.find('=')) != string::npos) { // Add previous item to list AddItem(Section, Item, Data); // Extract parameter name and data Item = string(Line, 0, Line.find('=')-1); Data = string(Line, Line.find('=')+1, string::npos); } else Data += ' ' + Line; // Concatenate lines } // Add last item AddItem(Section, Item, Data); } // Add item to configuration list void EvidenceConfig::AddItem(string Section, string Parameter, string Data) { // Clean up strings RemoveSpaces(Parameter); RemoveSpaces(Data); if (Section.empty() || Parameter.empty() || Data.empty()) return; // Prepare new item of configuration list struct Item New; New.Name = Section + ' ' + Parameter; New.Data = Data; // Add to configuration list if (pthread_mutex_lock(&Mutex) != 0) Message(ERROR, "pthread_mutex_lock() failed in ConfigChanged()"); List.push_back(New); if (pthread_mutex_unlock(&Mutex) != 0) Message(ERROR, "pthread_mutex_unlock() failed in ConfigChanged()"); } // Removes whitespace string EvidenceConfig::RemoveSpaces(string &Text) { // Remove leading spaces while (!Text.empty() && isspace(Text[0])) Text.erase(0, 1); // Remove trailing spaces while (!Text.empty() && isspace(Text[Text.size()-1])) Text.erase(Text.size()-1); // Remove multiple spaces while (Text.find(" ") != string::npos) Text.erase(Text.find(" "), 1); return Text; } // // Declaring class static ensures destructor is called when exit() is invoked // int main(int argc, char *argv[]) { static EvidenceConfig Config(argc<2 ? DEFAULT_CONFIG : argv[1]); int Notify; struct inotify_event Event; if ((Notify = inotify_init()) == -1) { Config.Message(EvidenceConfig::WARN, "inotify_init() failed, cannot monitor changes of configuration file (%s)\n", strerror(errno)); } else if (inotify_add_watch(Notify, argc<2 ? DEFAULT_CONFIG : argv[1], IN_MODIFY) == -1) { Config.Message(EvidenceConfig::WARN, "Could not set inotify watch on configuration file (%s)\n", strerror(errno)); close(Notify); Notify = -1; } // Sleep until file changes or signal caught while (!Config.ExitRequest) { if (Notify != -1) { read(Notify, &Event, sizeof(Event)); Config.ConfigChanged(); } else pause(); } // Closing will also free all inotify watches if (Notify != -1) close(Notify); }