source: Evidence/Config.cc@ 226

Last change on this file since 226 was 224, checked in by ogrimm, 14 years ago
Message severity encoded now using standard DIM structure, other updates
File size: 8.2 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 // Initialise mutex (errno is not set by pthread_mutex_init())
70 if (pthread_mutex_init(&Mutex, NULL) != 0) {
71 Message(FATAL, "pthread_mutex_init() failed");
72 }
73
74 // Open configuration file
75 if ((File = fopen(Filename, "r")) == NULL) {
76 Message(FATAL, "Could not open configuration file '%s' (%s)", Filename, strerror(errno));
77 }
78
79 // Disable buffering, so file modifications are immediately seen
80 if (setvbuf(File, NULL, _IONBF, 0) != 0) {
81 Message(WARN, "Error setting configuration file '%s' to unbuffered mode", Filename);
82 }
83
84 // Create DIM services
85 ConfigChanged();
86}
87
88// Destructor
89EvidenceConfig::~EvidenceConfig() {
90
91 if (File != NULL && fclose(File) != 0) Message(ERROR, "Error closing configuration file (%s)", strerror(errno));
92
93 delete ConfigModified;
94 delete ConfigContent;
95 delete[] FileContent;
96
97 if (pthread_mutex_destroy(&Mutex) != 0) Message(ERROR, "pthread_mutex_destroy() failed");
98}
99
100
101// Implementation of response to configuration request
102void EvidenceConfig::rpcHandler() {
103
104 string Response;
105
106 // Lock because ConfigChange() might access concurrently
107 if (pthread_mutex_lock(&Mutex) != 0) Message(ERROR, "pthread_mutex_lock() failed in rpcHandler()");
108
109 // Search for config data in list (default response is empty string)
110 for (int i=0; i<List.size(); i++) {
111 if (List[i].Name == getString()) Response = List[i].Data;
112 }
113
114 // Unlock
115 if (pthread_mutex_unlock(&Mutex) != 0) Message(ERROR, "pthread_mutex_unlock() failed in rpcHandler()");
116
117 // Send data and update Status
118 setData((char *) Response.c_str());
119
120 Message(INFO, "Client '%s' (ID %d) requested '%s'. Send '%s'.",
121 DimServer::getClientName(),
122 DimServer::getClientId(),
123 getString(), Response.c_str());
124}
125
126
127// Signalisation of configuration change
128void EvidenceConfig::ConfigChanged() {
129
130 static int ModifyTime;
131
132 //
133 // Part A: Handle updates to DIM services
134 //
135
136 // Access status information for configuration file (if fileno() returns -1, fstat() reports error)
137 struct stat Stat;
138 if (fstat(fileno(File), &Stat) == -1) {
139 Message(ERROR, "Error with stat() system call for configuration file, cannot update configuration file information (%s)", strerror(errno));
140 delete ConfigModified;
141 delete ConfigContent;
142 ConfigModified = NULL;
143 ConfigContent = NULL;
144 return;
145 }
146
147 // Create/update DIM service to indicate changes of configuration file
148 ModifyTime = Stat.st_mtime;
149 if (ConfigModified == NULL) ConfigModified = new DimService (SERVER_NAME"/ModifyTime", ModifyTime);
150 else ConfigModified->updateService();
151
152 // Read new configuration file (enfore \0 termination)
153 char *NewContent = new char [Stat.st_size+1];
154 rewind(File);
155 if (fread(NewContent, sizeof(char), Stat.st_size, File) != Stat.st_size) {
156 Message(FATAL, "Could not read configuration file");
157 }
158 NewContent[Stat.st_size] = '\0';
159
160 // Create/update DIM service that contains the current configuration file
161 if (ConfigContent == NULL )ConfigContent = new DimService(SERVER_NAME"/ConfigData", NewContent);
162 else ConfigContent->updateService(NewContent);
163
164 delete[] FileContent;
165 FileContent = NewContent;
166
167 //
168 // Part B: Interpret configuration list
169 //
170
171 stringstream In(FileContent), Out;
172 string Section, Item, Line, Data;
173
174 // First clean up and concatenate lines
175 while (getline(In, Line).good()) {
176 // Remove comments
177 if (Line.find('#') != string::npos) Line.erase(Line.find('#'));
178 // Replace all tabs by spaces
179 while (Line.find("\t") != string::npos) Line[Line.find("\t")] = ' ';
180 // Remove empty lines
181 if (RemoveSpaces(Line).empty()) continue;
182 // Concatenate if line ends with '\'
183 if (Line[Line.size()-1] != '\\') Out << Line << endl;
184 else Out << Line.erase(Line.size()-1);
185 };
186
187 In.str(Out.str());
188 In.clear();
189
190 // Interpret data
191 while (getline(In, Line).good()) {
192
193 // Check if current line is section heading (contains only [xxx])
194 if (Line.find('[')==0 && Line.find(']')==Line.size()-1) {
195 // Add previous item to list (if any)
196 AddItem(Section, Item, Data);
197 Item.clear();
198 Data.clear();
199 // Extract new section name
200 Section = Line.substr(1, Line.size()-2);
201 continue;
202 }
203
204 // Check if current line contains equal sign (defines new item name)
205 if((Line.find('=')) != string::npos) {
206 // Add previous item to list
207 AddItem(Section, Item, Data);
208 // Extract parameter name and data
209 Item = string(Line, 0, Line.find('=')-1);
210 Data = string(Line, Line.find('=')+1, string::npos);
211 }
212 else Data += ' ' + Line; // Concatenate lines
213 }
214 // Add last item
215 AddItem(Section, Item, Data);
216}
217
218// Add item to configuration list
219void EvidenceConfig::AddItem(string Section, string Parameter, string Data) {
220
221 // Clean up strings
222 RemoveSpaces(Parameter);
223 RemoveSpaces(Data);
224 if (Section.empty() || Parameter.empty() || Data.empty()) return;
225
226 // Prepare new item of configuration list
227 struct Item New;
228 New.Name = Section + ' ' + Parameter;
229 New.Data = Data;
230
231 // Add to configuration list
232 if (pthread_mutex_lock(&Mutex) != 0) Message(ERROR, "pthread_mutex_lock() failed in ConfigChanged()");
233 List.push_back(New);
234 if (pthread_mutex_unlock(&Mutex) != 0) Message(ERROR, "pthread_mutex_unlock() failed in ConfigChanged()");
235}
236
237// Removes whitespace
238string EvidenceConfig::RemoveSpaces(string &Text) {
239
240 // Remove leading spaces
241 while (!Text.empty() && isspace(Text[0])) Text.erase(0, 1);
242 // Remove trailing spaces
243 while (!Text.empty() && isspace(Text[Text.size()-1])) Text.erase(Text.size()-1);
244 // Remove multiple spaces
245 while (Text.find(" ") != string::npos) Text.erase(Text.find(" "), 1);
246
247 return Text;
248}
249
250//
251// Declaring class static ensures destructor is called when exit() is invoked
252//
253int main(int argc, char *argv[]) {
254
255 static EvidenceConfig Config(argc<2 ? DEFAULT_CONFIG : argv[1]);
256
257 int Notify;
258 struct inotify_event Event;
259
260 if ((Notify = inotify_init()) == -1) {
261 Config.Message(EvidenceConfig::WARN, "inotify_init() failed, cannot monitor changes of configuration file (%s)\n", strerror(errno));
262 }
263 else if (inotify_add_watch(Notify, argc<2 ? DEFAULT_CONFIG : argv[1], IN_MODIFY) == -1) {
264 Config.Message(EvidenceConfig::WARN, "Could not set inotify watch on configuration file (%s)\n", strerror(errno));
265 close(Notify);
266 Notify = -1;
267 }
268
269 // Sleep until file changes or signal caught
270 while (!Config.ExitRequest) {
271 if (Notify != -1) {
272 read(Notify, &Event, sizeof(Event));
273 Config.ConfigChanged();
274 }
275 else pause();
276 }
277
278 // Closing will also free all inotify watches
279 if (Notify != -1) close(Notify);
280}
Note: See TracBrowser for help on using the repository browser.