source: Evidence/Evidence.cc@ 229

Last change on this file since 229 was 229, checked in by ogrimm, 14 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: 16.0 KB
Line 
1/********************************************************************\
2
3 General code to start a server of the Evidence Control System
4
5 - The server is started with the given name.
6 - DIM exit and error handlers are implemented.
7 - A Message service is published with severity encoding. It can be updated
8 with the Message() method. The text will also be logged.
9 - If the severity of a Message() call is FATAL, exit() will be called (with
10 this severity, the call to Message() is guranteed not to return).
11 - Configuration data can be requested by GetConfig() and non-blocking by GetConfigNB().
12 - If the configuration file changes the signal SIGUSR1 is emitted which is caught by the standard
13 signal handler. The handler invokes ConfigChanged() which can be redefined by the user application.
14 The signal is delivered only to the main thread (where the constructor is executed) and thus
15 blocking rpc can be made from it.
16 - Signal handlers to ignore common signals are installed.
17 These signals will then cause pause() to return which can be used
18 by the application to terminate gracefully.
19 - The static method ToString() converts the contents of a DIMInfo service into text
20 - A terminate-handler is installed for catching unhandled C++ exceptions.
21
22 Oliver Grimm, June 2010
23
24\********************************************************************/
25
26#include "Evidence.h"
27using namespace std;
28
29EvidenceServer *ThisServer;
30
31//
32// Internal configuration class of EvidenceServer
33//
34// Data that might be accessed by two threads are protected by mutex
35
36// Constructor
37EvidenceServer::Config::Config(string Name): DimRpcInfo("ConfigRequest", NO_LINK), Name(Name) {
38
39 // Initialise
40 int Ret;
41 if ((Ret = pthread_mutex_init(&Mutex, NULL)) != 0) {
42 ThisServer->Message(ThisServer->FATAL, "pthread_mutex_init() failed in Config constructor (%s)", strerror(Ret));
43 }
44 CurrentItem = string();
45 ConfigTimeStamp = 0;
46
47 // SIGUSR2 delivered to this thread if configuration file changes
48 ThreadID = pthread_self();
49
50 // Subscribe to be informed on configuration file change
51 Service = new DimInfo("Config/ModifyTime", NO_LINK, this);
52}
53
54// Destructor
55EvidenceServer::Config::~Config() {
56
57 delete Service;
58
59 int Ret;
60 if ((Ret = pthread_mutex_destroy(&Mutex)) != 0) {
61 ThisServer->Message(ThisServer->ERROR, "pthread_mutex_destroy() failed in Config destructor (%s)", strerror(Ret));
62 }
63}
64
65// Track last modification time of configuration file
66void EvidenceServer::Config::infoHandler() {
67
68 Lock();
69 ConfigTimeStamp = getInfo()->getInt();
70 Unlock();
71
72 if (pthread_kill(ThreadID, SIGUSR2) != 0) {
73 ThisServer->Message(ThisServer->WARN, "Could not send signal SIGUSR2 to main thread");
74 }
75}
76
77// Receive answer to remote procedure call
78void EvidenceServer::Config::rpcInfoHandler(){
79
80 Lock();
81 // Update map
82 List[CurrentItem].Value = string(getString(), getSize()-1);
83 List[CurrentItem].Time = ConfigTimeStamp;
84 // Clear to allow new rpc call
85 CurrentItem.clear();
86 Unlock();
87}
88
89// Return configuration data if still up to date or empty string otherwise
90string EvidenceServer::Config::GetConfig(string Item, string Default) {
91
92 string Result;
93
94 // If up-to-date data in configuration list available, return this
95 Lock();
96 if ((List.count(Item) > 0) && (List[Item].Time >= ConfigTimeStamp)) Result = List[Item].Value;
97 Unlock();
98 if (!Result.empty()) return Result;
99
100 // Blocking configuration request if in main thread
101 if (pthread_self() == ThreadID) {
102 DimRpcInfo Config((char *) "ConfigRequest", NO_LINK);
103 Config.setData((char *) (Name + " " + Item).c_str());
104
105 // Check if successful
106 if (!EvidenceServer::ServiceOK(&Config)) {
107 if (Default.empty()) {
108 ThisServer->Message(ThisServer->FATAL, "Configuration server unreachable, can't retrieve '%s'", Item.c_str());
109 }
110 else Result = Default;
111 }
112 else {
113 if (Config.getSize() == 0) Result = Default;
114 else Result = string(Config.getString(), Config.getSize()-1); // Retrieve string safely
115 }
116
117 // Update configuration map
118 if (!Result.empty()) {
119 Lock();
120 List[Item].Value = Result;
121 List[Item].Time = ConfigTimeStamp;
122 Unlock();
123 }
124 }
125
126 // Non-blocking configuration request fro other threads
127 if (pthread_self() != ThreadID) {
128 Lock();
129 if (List.count(Item) > 0) {
130 // New request possible only when answer to previous request received
131 if (CurrentItem.empty()) {
132 CurrentItem = Item;
133 setData(((char *) (Name + " " + Item).c_str()));
134 }
135
136 // Return current value
137 Result = List[Item].Value;
138 Unlock();
139 }
140 }
141
142 return Result;
143}
144
145// Locking and unlocking for list access. Signal SIGUSR2 is also blocked.
146void EvidenceServer::Config::Lock() {
147
148 int Ret = 0;
149 sigset_t Set;
150 //printf("Locking %u\n", pthread_self());
151 Ret += abs(sigemptyset(&Set));
152 Ret += abs(sigaddset(&Set, SIGUSR2));
153 Ret += abs(pthread_sigmask(SIG_BLOCK, &Set, NULL));
154 Ret += abs(pthread_mutex_lock(&Mutex));
155
156 if (Ret != 0) {
157 ThisServer->Message(ThisServer->FATAL, "Thread related call failed in Config::Lock()");
158 }
159}
160
161void EvidenceServer::Config::Unlock() {
162
163 int Ret = 0;
164 sigset_t Set;
165 //printf(" Unlocking %u\n", pthread_self());
166
167 Ret += abs(pthread_mutex_unlock(&Mutex));
168 Ret += abs(sigemptyset(&Set));
169 Ret += abs(sigaddset(&Set, SIGUSR2));
170 Ret += abs(pthread_sigmask(SIG_UNBLOCK, &Set, NULL));
171
172 if (Ret != 0) {
173 ThisServer->Message(ThisServer->FATAL, "Thread related call failed in Config::Unlock()");
174 }
175}
176
177
178//////////////////////////
179// EvidenceServer Class //
180//////////////////////////
181
182// Constructor starts server with given name
183EvidenceServer::EvidenceServer(const char *ServerName): Name(ServerName) {
184
185 // Initialize
186 MessageService = NULL;
187 MessageData = NULL;
188 ExitRequest = false;
189 ThisServer = this;
190
191 // Catch some signals
192 signal(SIGQUIT, &SignalHandler); // CTRL-Backspace
193 signal(SIGTERM, &SignalHandler); // Termination signal
194 signal(SIGINT, &SignalHandler); // CTRL-C
195 signal(SIGHUP, &SignalHandler); // Terminal closed
196
197 struct sigaction S;
198 S.sa_handler = &SignalHandler;
199 S.sa_flags = SA_RESTART;
200 sigaction(SIGUSR2, &S, NULL);
201
202 // Catch C++ unhandled exceptions
203 set_terminate(Terminate);
204
205 // Configuration class (instantiate after signal handling for SIGUSR2 installed)
206 ConfClass = new class Config(Name);
207
208 // Message service and initial message
209 MessageService = new DimService((Name+"/Message").c_str(), (char *) "I:1;C", NULL, 0);
210
211 string Rev(EVIDENCE_REVISION);
212 Message(INFO, "Server started (%s, compiled %s %s)", (Rev.substr(1, Rev.size()-3)).c_str(),__DATE__, __TIME__);
213
214 // Start server
215 start(ServerName);
216 addExitHandler(this);
217}
218
219// Destructor: Free memory
220EvidenceServer::~EvidenceServer() {
221
222 Message(INFO, "Server stopped");
223
224 delete ConfClass;
225 delete MessageService;
226 delete MessageData;
227}
228
229// DIM exit handler
230void EvidenceServer::exitHandler(int Code) {
231
232 Message(INFO, "Exit handler called (DIM exit code %d)", Code);
233 exit(EXIT_SUCCESS);
234}
235
236// DIM error handler
237void EvidenceServer::errorHandler(int Severity, int Code, char *Text) {
238 Message(ERROR, "%s (DIM error code %d, DIM severity %d)\n", Text, Code, Severity);
239}
240
241// Set server message (if Severity is FATAL, exit() will be invoked)
242void EvidenceServer::Message(MessageType Severity, const char *Format, ...) {
243
244 static const char* StateString[] = {"Info", "Warn", "Error", "Fatal"};
245 static char ErrorString[] = "vasprintf() failed in Message()";
246 char *Text;
247
248 // Assemble message from application
249 va_list ArgumentPointer;
250 va_start(ArgumentPointer, Format);
251 if (vasprintf(&Text, Format, ArgumentPointer) == -1) {
252 Text = ErrorString;
253 Severity = ERROR;
254 }
255 va_end(ArgumentPointer);
256
257 // Generate new Message structure and free text
258 struct Message *NewMsg = (struct Message *) new char [sizeof(struct Message)+strlen(Text)+1];
259 NewMsg->Severity = Severity;
260 strcpy(NewMsg->Text, Text);
261 if (Text != ErrorString) free(Text);
262
263 // Send message to console and log file
264 printf("%s (%s): %s\n", MessageService->getName(), StateString[Severity], NewMsg->Text);
265 SendToLog("%s (%s): %s", MessageService->getName(), StateString[Severity], NewMsg->Text);
266
267 // Update DIM message service, then delete old message
268 if (MessageService != NULL) {
269 MessageService->updateService(NewMsg, sizeof(struct Message)+strlen(NewMsg->Text)+1);
270 }
271 delete MessageData;
272 MessageData = NewMsg;
273
274 // Terminate if severity if FATAL
275 if (Severity == FATAL) exit(EXIT_FAILURE);
276}
277
278
279// Set to central logging server with non-blocking command (may be used in DIM handler)
280void EvidenceServer::SendToLog(const char *Format, ...) {
281
282 char *Buffer;
283 int Ret;
284
285 // Evaluate variable argument list
286 va_list ArgumentPointer;
287 va_start(ArgumentPointer, Format);
288 Ret = vasprintf(&Buffer, Format, ArgumentPointer);
289 va_end(ArgumentPointer);
290
291 // Send to logger
292 if (Ret != -1) {
293 DimClient::sendCommandNB("DColl/Log", Buffer);
294 free (Buffer);
295 }
296 else Message(ERROR, "Could not create logging text in SendToLog(), vasprintf() failed");
297}
298
299
300// Get configuration data
301//
302// Program terminates if data is missing and no default given. Actual configuration
303// request will be made only if config file has modification since last request.
304// If called from infoHandler(), a non-blocking request will be made
305string EvidenceServer::GetConfig(string Item, string Default) {
306
307 string Result = ConfClass->GetConfig(Item, Default);
308 if (Result.empty()) Message(FATAL, "Missing configuration data '%s'", Item.c_str());
309 return Result;
310}
311
312// ====== Static methods ======
313
314// Signal handler (causes pause() and other syscalls to return)
315void EvidenceServer::SignalHandler(int Signal) {
316
317 static bool Called = false;
318
319 // If SIGUSR2, invoke call-back for configuraton change
320 if (Signal == SIGUSR2) {
321 ThisServer->ConfigChanged();
322 return;
323 }
324
325 // At first invocation just request exit
326 if (!Called) {
327 Called = true;
328 ThisServer->ExitRequest = true;
329 return;
330 }
331
332 // If invoked twice, call exit()
333 ThisServer->Message(ThisServer->WARN, "Signal handler called again, invoking exit() (signal %d)", Signal);
334 exit(EXIT_FAILURE);
335}
336
337// C++ exception handler (derived from gcc __verbose_terminate_handler())
338void EvidenceServer::Terminate() {
339
340 static char Msg[STATUS_SIZE];
341 static bool Terminating = false;
342
343 if (Terminating) {
344 snprintf(Msg, sizeof(Msg), "%s: Terminate() called recursively, calling abort()", ThisServer->MessageService->getName());
345 printf("%s\n", Msg);
346 DimClient::sendCommandNB("DColl/Log", Msg);
347 abort();
348 }
349
350 Terminating = true;
351
352 // Make sure there was an exception; terminate is also called for an
353 // attempt to rethrow when there is no suitable exception.
354 type_info *Type = abi::__cxa_current_exception_type();
355 if (Type != NULL) {
356 int Status = -1;
357 char *Demangled = NULL;
358
359 Demangled = abi::__cxa_demangle(Type->name(), 0, 0, &Status);
360 snprintf(Msg, sizeof(Msg), "Terminate() called after throwing an instance of '%s'", Status==0 ? Demangled : Type->name());
361 free(Demangled);
362
363 // If exception derived from std::exception, more information.
364 try { __throw_exception_again; }
365 catch (exception &E) {
366 snprintf(Msg+strlen(Msg), sizeof(Msg)-strlen(Msg), " (what(): %s)", E.what());
367 }
368 catch (...) { }
369 }
370 else snprintf(Msg, sizeof(Msg), "Terminate() called without an active exception");
371
372 ThisServer->Message(FATAL, Msg);
373}
374
375
376// Translates DIMInfo safely to string (assures no inivalid memory accesses are made)
377string EvidenceServer::ToString(DimInfo *I) {
378
379 ostringstream Text;
380
381 // Safety check
382 if (I->getSize() < 1) return string();
383
384 // 'Message' service format handled here
385 if (strcmp(I->getFormat(), "I:1;C") == 0 && I->getSize()>=(int) sizeof(struct Message)) {
386 struct Message *Msg = (struct Message *) I->getData();
387 // Safely extract string and limit to length of C string (padding might make it longer)
388 string MsgText(Msg->Text, I->getSize()-sizeof(struct Message));
389 //MsgText.erase(strlen(MsgText.c_str()));
390 Text << Msg->Severity << " " << MsgText.erase(strlen(MsgText.c_str()));
391
392 return Text.str();
393 }
394
395 // Structure: print hex representation
396 if (strlen(I->getFormat()) != 1) {
397 for (int i=0; i<I->getSize(); i++) {
398 Text << setw(2) << hex << *((char *) I->getData() + i) << " ";
399 }
400 return Text.str();
401 }
402
403 // String if format "C" and terminated with \0
404 if (strcmp(I->getFormat(),"C")==0 && *((char *) I->getData()+I->getSize()-1)=='\0') {
405 Text << I->getString();
406 return Text.str();
407 }
408
409 // Number array
410 int Size;
411 switch (*(I->getFormat())) {
412 case 'C': Size = sizeof(char); break;
413 case 'I':
414 case 'L': Size = sizeof(int); break;
415 case 'S': Size = sizeof(short); break;
416 case 'F': Size = sizeof(float); break;
417 case 'D': Size = sizeof(double); break;
418 case 'X': Size = sizeof(long long); break;
419 default: return string();
420 }
421
422 for (int i=0; i<I->getSize()/Size; i++) {
423 // Space between entries
424 if (i != 0) Text << " ";
425
426 // Translate data
427 switch (*(I->getFormat())) {
428 case 'C': Text << *((char *) I->getData() + i);
429 break;
430 case 'I':
431 case 'L': Text << *((int *) I->getData() + i);
432 break;
433 case 'S': Text << *((short *) I->getData() + i);
434 break;
435 case 'F': Text << *((float *) I->getData() + i);
436 break;
437 case 'D': Text << *((double *) I->getData() + i);
438 break;
439 case 'X': Text << *((long long *) I->getData() + i);
440 break;
441 }
442 }
443
444 return Text.str();
445}
446
447
448// Checks if service contents indicates not available
449bool EvidenceServer::ServiceOK(DimInfo *Item) {
450
451 return !((Item->getSize() == strlen(NO_LINK)+1) &&
452 (memcmp(Item->getData(), NO_LINK, Item->getSize()) == 0));
453}
454
455bool EvidenceServer::ServiceOK(DimRpcInfo *Item) {
456
457 return !((Item->getSize() == strlen(NO_LINK)+1) &&
458 (memcmp(Item->getData(), NO_LINK, Item->getSize()) == 0));
459}
460
461
462// Tokenize std::string using given delimeter list
463vector<string> EvidenceServer::Tokenize(const string &String, const string &Delimiters) {
464
465 vector<string> Tokens;
466 string::size_type Next, EndNext=0;
467
468 while (EndNext != string::npos) {
469 // Find next token
470 Next = String.find_first_not_of(Delimiters, EndNext);
471 EndNext = String.find_first_of(Delimiters, Next);
472
473 // Stop if end of string reached
474 if (Next == string::npos) break;
475
476 // Add token to vector.
477 Tokens.push_back(String.substr(Next, EndNext - Next));
478 }
479
480 return Tokens;
481}
482
483
484///////////////////////////
485// EvidenceHistory Class //
486///////////////////////////
487
488// Organisaztion of history buffer
489// F | T S D | T S D | 0 0 ...... | T S D | T S D | 0 -1
490//
491// F: Offset of oldest entry T: Time S: Size D: Data
492// F, T, S: int
493
494// Marker for history buffer
495const struct EvidenceHistory::Item EvidenceHistory::WrapMark = {0, -1, {}};
496const struct EvidenceHistory::Item EvidenceHistory::EndMark = {0, 0, {}};
497
498// Constructor
499EvidenceHistory::EvidenceHistory(std::string Name): Name(Name) {
500
501 Buffer = NULL;
502}
503
504// Destructor
505EvidenceHistory::~EvidenceHistory() {
506
507 delete[] Buffer;
508}
509
510// Requests service history (returns true if data received OK)
511bool EvidenceHistory::GetHistory() {
512
513 DimRpcInfo R((char *) "ServiceHistory", NO_LINK);
514 R.setData((char *) Name.c_str());
515
516 // Check if data OK
517 if (!EvidenceServer::ServiceOK(&R)) return false;
518 if (R.getSize() == 0) return false;
519
520 // Copy data to buffer
521 delete[] Buffer;
522 BufferSize = R.getSize();
523 Buffer = new char [BufferSize];
524
525 memcpy(Buffer, R.getData(), BufferSize);
526 DataStart = Buffer + strlen(Buffer) + 1;
527 Rewind();
528
529 return true;
530}
531
532// Returns next item in history buffer
533const struct EvidenceHistory::Item *EvidenceHistory::Next() {
534
535 if (Buffer == NULL) return NULL;
536
537 // Check for wrap around
538 if (memcmp(Pointer, &WrapMark, sizeof(WrapMark)) == 0) Pointer = (struct Item *) (DataStart + sizeof(int));
539 // Check if at end of ring buffer
540 if (memcmp(Pointer, &EndMark, sizeof(EndMark)) == 0) return NULL;
541
542 const struct Item *Ret = Pointer;
543 Pointer = (struct Item *) ((char *) (Ret + 1) + Ret->Size);
544
545 return Ret;
546}
547
548// Reset to start of buffer
549void EvidenceHistory::Rewind() {
550
551 if (Buffer != NULL) Pointer = (struct Item *) (DataStart + (*(int *) DataStart));
552}
553
554// Return DIM format string of service (NULL if no data)
555char *EvidenceHistory::GetFormat() {
556
557 return Buffer;
558}
Note: See TracBrowser for help on using the repository browser.