/********************************************************************\ 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 parser removes all tabs, multiple, leading and trailing spaces, and concatenates all lines until the next item. A mutex is used for preventing concurrent access to the configuration data from the methods rpcHandler() and ConfigChanged(). Oliver Grimm, July 2010 \********************************************************************/ #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: map Map; FILE *File; char *FileContent; DimService *ConfigModified; DimService *ConfigContent; 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; // 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; } // Implementation of response to configuration request void EvidenceConfig::rpcHandler() { string Response, Item = ToString((char *) "C", getData(), getSize()); // Search for config data in list (default response is empty string) // Lock because ConfigChange() might also access concurrently Lock(); if (Map.count(Item) != 0) Response = Map[Item]; Unlock(); // Send data and update Status setData((char *) Response.c_str()); Message(INFO, "Client '%s' (ID %d) requested '%s'. Send '%s'.", DimServer::getClientName(), DimServer::getClientId(), Item.c_str(), Response.c_str()); } // Signalisation of configuration change void EvidenceConfig::ConfigChanged() { static int ModifyTime; // // Part A: Handle updates to DIM services // // Access status information for configuration file (if fileno() returns -1, fstat() reports error) struct stat Stat; if (fstat(fileno(File), &Stat) == -1) { Message(ERROR, "Error with stat() system call for configuration file, cannot update configuration file information (%s)", strerror(errno)); delete ConfigModified; delete ConfigContent; ConfigModified = NULL; ConfigContent = NULL; return; } // Create/update DIM service to indicate changes of configuration file ModifyTime = Stat.st_mtime; if (ConfigModified == NULL) ConfigModified = new DimService (SERVER_NAME"/ModifyTime", ModifyTime); else ConfigModified->updateService(); // 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; // Add to configuration list Lock(); Map[Section + " " + Parameter] = Data; Unlock(); } // 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[]) { if (argc != 2) { printf("Usage: %s \n", argv[0]); exit(EXIT_FAILURE); } static EvidenceConfig 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, 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) { if (read(Notify, &Event, sizeof(Event)) == sizeof(Event)) Config.ConfigChanged(); } else pause(); } // Closing will also free all inotify watches if (Notify != -1) close(Notify); }