source: Evidence/Config.cc@ 216

Last change on this file since 216 was 216, checked in by ogrimm, 15 years ago
Changed service 'Status' to 'Message' for clarity, added client information in log file entries
File size: 7.1 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
51 public:
52 EvidenceConfig(const char *);
53 ~EvidenceConfig();
54
55 void ConfigChanged();
56};
57
58
59// Constructor
60EvidenceConfig::EvidenceConfig(const char *Filename):
61 DimRpc("ConfigRequest", "C", "C"), EvidenceServer(SERVER_NAME) {
62
63 ConfigModified = NULL; // Will be allocated in ConfigChanged()
64 ConfigContent = NULL;
65 FileContent = NULL;
66
67 // Initialise mutex (errno is not set by pthread_mutex_init())
68 if (pthread_mutex_init(&Mutex, NULL) != 0) {
69 Message(FATAL, "pthread_mutex_init() failed");
70 }
71
72 // Open configuration file
73 if ((File = fopen(Filename, "r")) == NULL) {
74 Message(FATAL, "Could not open configuration file '%s' (%s)\n", Filename, strerror(errno));
75 }
76
77 // Disable buffering, so file modifications are immediately seen
78 if (setvbuf(File, NULL, _IONBF, 0) != 0) {
79 Message(WARN, "Error setting configuration file '%s' to unbuffered mode", Filename);
80 }
81
82 // Create DIM services
83 ConfigChanged();
84}
85
86// Destructor
87EvidenceConfig::~EvidenceConfig() {
88
89 if (File != NULL && fclose(File) != 0) Message(ERROR, "Error closing configuration file (%s)", strerror(errno));
90
91 delete ConfigModified;
92 delete ConfigContent;
93 delete[] FileContent;
94
95 if (pthread_mutex_destroy(&Mutex) != 0) Message(ERROR, "pthread_mutex_destroy() failed");
96}
97
98
99// Implementation of response to configuration request
100void EvidenceConfig::rpcHandler() {
101
102 string Response;
103
104 // Lock because ConfigChange() might access concurrently
105 if (pthread_mutex_lock(&Mutex) != 0) Message(ERROR, "pthread_mutex_lock() failed in rpcHandler()");
106
107 // Search for config data in list (default response is empty string)
108 for (int i=0; i<List.size(); i++) {
109 if (List[i].Name == getString()) Response = List[i].Data;
110 }
111
112 // Unlock
113 if (pthread_mutex_unlock(&Mutex) != 0) Message(ERROR, "pthread_mutex_unlock() failed in rpcHandler()");
114
115 // Send data and update Status
116 setData((char *) Response.c_str());
117
118 Message(INFO, "Client '%s' (ID %d) requested '%s'. Send '%s'.",
119 DimServer::getClientName(),
120 DimServer::getClientId(),
121 getString(), Response.c_str());
122}
123
124
125// Signalisation of configuration change
126void EvidenceConfig::ConfigChanged() {
127
128 static int ModifyTime;
129
130 //
131 // Part A: Handle updates to DIM services
132 //
133
134 // Access status information for configuration file (if fileno() returns -1, fstat() reports error)
135 struct stat Stat;
136 if (fstat(fileno(File), &Stat) == -1) {
137 Message(ERROR, "Error with stat() system call for configuration file, cannot update configuration file information (%s)", strerror(errno));
138 delete ConfigModified;
139 delete ConfigContent;
140 ConfigModified = NULL;
141 ConfigContent = NULL;
142 return;
143 }
144
145 // Create/update DIM service to indicate changes of configuration file
146 ModifyTime = Stat.st_mtime;
147 if (ConfigModified == NULL) ConfigModified = new DimService (SERVER_NAME"/ModifyTime", ModifyTime);
148 else ConfigModified->updateService();
149
150 // Read new configuration file (enfore \0 termination)
151 char *NewContent = new char [Stat.st_size+1];
152 rewind(File);
153 if (fread(NewContent, sizeof(char), Stat.st_size, File) != Stat.st_size) {
154 Message(FATAL, "Could not read configuration file");
155 }
156 NewContent[Stat.st_size] = '\0';
157
158 // Create/update DIM service that contains the current configuration file
159 if (ConfigContent == NULL )ConfigContent = new DimService(SERVER_NAME"/ConfigData", NewContent);
160 else ConfigContent->updateService(NewContent);
161
162 delete[] FileContent;
163 FileContent = NewContent;
164
165 //
166 // Part B: Interpret configuration list
167 //
168
169 stringstream In(FileContent), Out;
170 string Line;
171 struct Item New;
172 size_t Pos;
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 leading spaces
181 while (!Line.empty() && isspace(Line[0])) Line.erase(0, 1);
182 // Remove trailing spaces
183 while (!Line.empty() && isspace(Line[Line.size()-1])) Line.erase(Line.size()-1);
184 // Remove empty lines
185 if (Line.empty()) continue;
186 // Concatenate if line ends with '+'
187 if (Line[Line.size()-1] != '+') Out << Line << endl;
188 else Out << Line.erase(Line.size()-1);
189 };
190
191 In.str(Out.str());
192 In.clear();
193
194 // Interpret data
195 while (getline(In, Line).good()) {
196 // Remove multiple spaces
197 while (Line.find(" ") != string::npos) Line.erase(Line.find(" "), 1);
198
199 // Find second space character
200 Pos = Line.find(" ", Line.find(" ") + 1);
201 if(Pos == string::npos) continue;
202
203 // Extract configuration name and data
204 New.Name = string(Line, 0, Pos);
205 New.Data = string(Line, Pos+1);
206
207 // Add to configuration list
208 if (pthread_mutex_lock(&Mutex) != 0) Message(ERROR, "pthread_mutex_lock() failed in ConfigChanged()");
209 List.push_back(New);
210 if (pthread_mutex_unlock(&Mutex) != 0) Message(ERROR, "pthread_mutex_unlock() failed in ConfigChanged()");
211 };
212}
213
214
215//
216// Declaring class static ensures destructor is called when exit() is invoked
217//
218int main(int argc, char *argv[]) {
219
220 static EvidenceConfig Config(argc<2 ? DEFAULT_CONFIG : argv[1]);
221
222 int Notify;
223 struct inotify_event Event;
224
225 if ((Notify = inotify_init()) == -1) {
226 Config.Message(EvidenceConfig::WARN, "inotify_init() failed, cannot monitor changes of configuration file (%s)\n", strerror(errno));
227 }
228 else if (inotify_add_watch(Notify, argc<2 ? DEFAULT_CONFIG : argv[1], IN_MODIFY) == -1) {
229 Config.Message(EvidenceConfig::WARN, "Could not set inotify watch on configuration file (%s)\n", strerror(errno));
230 close(Notify);
231 Notify = -1;
232 }
233
234 // Sleep until file changes or signal caught
235 while (!Config.ExitRequest) {
236 if (Notify != -1) {
237 read(Notify, &Event, sizeof(Event));
238 Config.ConfigChanged();
239 }
240 else pause();
241 }
242
243 // Closing will also free all inotify watches
244 if (Notify != -1) close(Notify);
245}
Note: See TracBrowser for help on using the repository browser.