source: Evidence/Config.cc@ 251

Last change on this file since 251 was 229, checked in by ogrimm, 15 years ago
Config requests non-blocking if not made from main thread, adapted all servers to GetConfig() returning std::string, workaround for erroneous SERVICE_LIST
File size: 8.3 KB
Line 
1/********************************************************************\
2
3 Configuration server for the Evidence Control System
4
5 - The name of a configuration file can be given as command line argument
6 - If a configuration file change is detected through inotify, the service
7 "Config/ModifyTime" contains the last modification UNIX time.
8 - The contents of the configuration file is available through Config/ConfigData.
9 - The current parser removes all tabs, multiple, leading and trailing spaces, and
10 concatenates lines ending with '+'.
11
12 A mutex is used for preventing concurrent access to the configuration data from
13 the methods rpcHandler() and ConfigChanged().
14
15 Oliver Grimm, April 2010
16
17\********************************************************************/
18
19#define DEFAULT_CONFIG "../config/Evidence.conf"
20#define SERVER_NAME "Config"
21
22#include "Evidence.h"
23
24#include <ctype.h>
25#include <sys/stat.h>
26#include <sys/inotify.h>
27#include <sstream>
28
29using namespace std;
30
31//
32// Class derived from DimRpc
33//
34class EvidenceConfig: public DimRpc, public EvidenceServer {
35
36 private:
37 struct Item {
38 string Name;
39 string Data;
40 };
41 vector<struct Item> List;
42
43 FILE *File;
44 char *FileContent;
45 DimService *ConfigModified;
46 DimService *ConfigContent;
47 pthread_mutex_t Mutex;
48
49 void rpcHandler();
50 void AddItem(string, string, string);
51 string RemoveSpaces(string &);
52
53 public:
54 EvidenceConfig(const char *);
55 ~EvidenceConfig();
56
57 void ConfigChanged();
58};
59
60
61// Constructor
62EvidenceConfig::EvidenceConfig(const char *Filename):
63 DimRpc("ConfigRequest", "C", "C"), EvidenceServer(SERVER_NAME) {
64
65 ConfigModified = NULL; // Will be allocated in ConfigChanged()
66 ConfigContent = NULL;
67 FileContent = NULL;
68
69 // Signaling interferes with inotify() mechnism
70 signal(SIGUSR2, SIG_IGN);
71
72 // Initialise mutex (errno is not set by pthread_mutex_init())
73 if (pthread_mutex_init(&Mutex, NULL) != 0) {
74 Message(FATAL, "pthread_mutex_init() failed");
75 }
76
77 // Open configuration file
78 if ((File = fopen(Filename, "r")) == NULL) {
79 Message(FATAL, "Could not open configuration file '%s' (%s)", Filename, strerror(errno));
80 }
81
82 // Disable buffering, so file modifications are immediately seen
83 if (setvbuf(File, NULL, _IONBF, 0) != 0) {
84 Message(WARN, "Error setting configuration file '%s' to unbuffered mode", Filename);
85 }
86
87 // Create DIM services
88 ConfigChanged();
89}
90
91// Destructor
92EvidenceConfig::~EvidenceConfig() {
93
94 if (File != NULL && fclose(File) != 0) Message(ERROR, "Error closing configuration file (%s)", strerror(errno));
95
96 delete ConfigModified;
97 delete ConfigContent;
98 delete[] FileContent;
99
100 if (pthread_mutex_destroy(&Mutex) != 0) Message(ERROR, "pthread_mutex_destroy() failed");
101}
102
103
104// Implementation of response to configuration request
105void EvidenceConfig::rpcHandler() {
106
107 string Response;
108
109 // Lock because ConfigChange() might access concurrently
110 if (pthread_mutex_lock(&Mutex) != 0) Message(ERROR, "pthread_mutex_lock() failed in rpcHandler()");
111
112 // Search for config data in list (default response is empty string)
113 for (int i=0; i<List.size(); i++) {
114 if (List[i].Name == getString()) Response = List[i].Data;
115 }
116
117 // Unlock
118 if (pthread_mutex_unlock(&Mutex) != 0) Message(ERROR, "pthread_mutex_unlock() failed in rpcHandler()");
119
120 // Send data and update Status
121 setData((char *) Response.c_str());
122
123 Message(INFO, "Client '%s' (ID %d) requested '%s'. Send '%s'.",
124 DimServer::getClientName(),
125 DimServer::getClientId(),
126 getString(), Response.c_str());
127}
128
129
130// Signalisation of configuration change
131void EvidenceConfig::ConfigChanged() {
132
133 static int ModifyTime;
134
135 //
136 // Part A: Handle updates to DIM services
137 //
138
139 // Access status information for configuration file (if fileno() returns -1, fstat() reports error)
140 struct stat Stat;
141 if (fstat(fileno(File), &Stat) == -1) {
142 Message(ERROR, "Error with stat() system call for configuration file, cannot update configuration file information (%s)", strerror(errno));
143 delete ConfigModified;
144 delete ConfigContent;
145 ConfigModified = NULL;
146 ConfigContent = NULL;
147 return;
148 }
149
150 // Create/update DIM service to indicate changes of configuration file
151 ModifyTime = Stat.st_mtime;
152 if (ConfigModified == NULL) ConfigModified = new DimService (SERVER_NAME"/ModifyTime", ModifyTime);
153 else ConfigModified->updateService();
154
155 // Read new configuration file (enfore \0 termination)
156 char *NewContent = new char [Stat.st_size+1];
157 rewind(File);
158 if (fread(NewContent, sizeof(char), Stat.st_size, File) != Stat.st_size) {
159 Message(FATAL, "Could not read configuration file");
160 }
161 NewContent[Stat.st_size] = '\0';
162
163 // Create/update DIM service that contains the current configuration file
164 if (ConfigContent == NULL )ConfigContent = new DimService(SERVER_NAME"/ConfigData", NewContent);
165 else ConfigContent->updateService(NewContent);
166
167 delete[] FileContent;
168 FileContent = NewContent;
169
170 //
171 // Part B: Interpret configuration list
172 //
173
174 stringstream In(FileContent), Out;
175 string Section, Item, Line, Data;
176
177 // First clean up and concatenate lines
178 while (getline(In, Line).good()) {
179 // Remove comments
180 if (Line.find('#') != string::npos) Line.erase(Line.find('#'));
181 // Replace all tabs by spaces
182 while (Line.find("\t") != string::npos) Line[Line.find("\t")] = ' ';
183 // Remove empty lines
184 if (RemoveSpaces(Line).empty()) continue;
185 // Concatenate if line ends with '\'
186 if (Line[Line.size()-1] != '\\') Out << Line << endl;
187 else Out << Line.erase(Line.size()-1);
188 };
189
190 In.str(Out.str());
191 In.clear();
192
193 // Interpret data
194 while (getline(In, Line).good()) {
195
196 // Check if current line is section heading (contains only [xxx])
197 if (Line.find('[')==0 && Line.find(']')==Line.size()-1) {
198 // Add previous item to list (if any)
199 AddItem(Section, Item, Data);
200 Item.clear();
201 Data.clear();
202 // Extract new section name
203 Section = Line.substr(1, Line.size()-2);
204 continue;
205 }
206
207 // Check if current line contains equal sign (defines new item name)
208 if((Line.find('=')) != string::npos) {
209 // Add previous item to list
210 AddItem(Section, Item, Data);
211 // Extract parameter name and data
212 Item = string(Line, 0, Line.find('=')-1);
213 Data = string(Line, Line.find('=')+1, string::npos);
214 }
215 else Data += ' ' + Line; // Concatenate lines
216 }
217 // Add last item
218 AddItem(Section, Item, Data);
219}
220
221// Add item to configuration list
222void EvidenceConfig::AddItem(string Section, string Parameter, string Data) {
223
224 // Clean up strings
225 RemoveSpaces(Parameter);
226 RemoveSpaces(Data);
227 if (Section.empty() || Parameter.empty() || Data.empty()) return;
228
229 // Prepare new item of configuration list
230 struct Item New;
231 New.Name = Section + ' ' + Parameter;
232 New.Data = Data;
233
234 // Add to configuration list
235 if (pthread_mutex_lock(&Mutex) != 0) Message(ERROR, "pthread_mutex_lock() failed in ConfigChanged()");
236 List.push_back(New);
237 if (pthread_mutex_unlock(&Mutex) != 0) Message(ERROR, "pthread_mutex_unlock() failed in ConfigChanged()");
238}
239
240// Removes whitespace
241string EvidenceConfig::RemoveSpaces(string &Text) {
242
243 // Remove leading spaces
244 while (!Text.empty() && isspace(Text[0])) Text.erase(0, 1);
245 // Remove trailing spaces
246 while (!Text.empty() && isspace(Text[Text.size()-1])) Text.erase(Text.size()-1);
247 // Remove multiple spaces
248 while (Text.find(" ") != string::npos) Text.erase(Text.find(" "), 1);
249
250 return Text;
251}
252
253//
254// Declaring class static ensures destructor is called when exit() is invoked
255//
256int main(int argc, char *argv[]) {
257
258 static EvidenceConfig Config(argc<2 ? DEFAULT_CONFIG : argv[1]);
259
260 int Notify;
261 struct inotify_event Event;
262
263 if ((Notify = inotify_init()) == -1) {
264 Config.Message(EvidenceConfig::WARN, "inotify_init() failed, cannot monitor changes of configuration file (%s)\n", strerror(errno));
265 }
266 else if (inotify_add_watch(Notify, argc<2 ? DEFAULT_CONFIG : argv[1], IN_MODIFY) == -1) {
267 Config.Message(EvidenceConfig::WARN, "Could not set inotify watch on configuration file (%s)\n", strerror(errno));
268 close(Notify);
269 Notify = -1;
270 }
271
272 // Sleep until file changes or signal caught
273 while (!Config.ExitRequest) {
274 if (Notify != -1) {
275 if (read(Notify, &Event, sizeof(Event)) == sizeof(Event)) Config.ConfigChanged();
276 }
277 else pause();
278 }
279
280 // Closing will also free all inotify watches
281 if (Notify != -1) close(Notify);
282}
Note: See TracBrowser for help on using the repository browser.