source: Evidence/Evidence.cc@ 257

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