source: fact/Evidence/Evidence.cc@ 20115

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