source: fact/Evidence/Evidence.cc@ 13285

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