source: fact/Evidence/Evidence.cc@ 10531

Last change on this file since 10531 was 10143, checked in by ogrimm, 14 years ago
Non-blocking configuration request did not recognize default value
File size: 16.7 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;
95set<pthread_t> EvidenceServer::Threads;
96
97
98// Constructor
99EvidenceServer::EvidenceServer(string ServerName): Name(ServerName) {
100
101 // Initialize
102 MessageService = NULL;
103 MessageData = NULL;
104 ExitRequest = false;
105 This = this;
106 Threads.insert(pthread_self());
107
108 // Initialise mutex
109 int Ret;
110 pthread_mutexattr_t Attr;
111
112 if ((Ret = pthread_mutexattr_settype(&Attr, PTHREAD_MUTEX_ERRORCHECK)) != 0) {
113 Message(FATAL, "pthread_mutex_settype() failed (%s)", strerror(Ret));
114 }
115 if ((Ret = pthread_mutex_init(&Mutex, &Attr)) != 0) {
116 Message(FATAL, "pthread_mutex_init() failed (%s)", strerror(Ret));
117 }
118
119 // Catch some signals
120 signal(SIGQUIT, &SignalHandler); // CTRL-Backspace
121 signal(SIGTERM, &SignalHandler); // Termination signal
122 signal(SIGINT, &SignalHandler); // CTRL-C
123 signal(SIGHUP, &SignalHandler); // Terminal closed
124
125 // Catch C++ unhandled exceptions
126 set_terminate(Terminate);
127
128 // Message service and initial message
129 MessageService = new DimService((Name+"/Message").c_str(), (char *) "I:1;C", NULL, 0);
130
131 string Rev(EVIDENCE_REVISION);
132 Message(INFO, "Server started (%s, compiled %s %s)", (Rev.substr(1, Rev.size()-3)).c_str(),__DATE__, __TIME__);
133
134 // Configuration class
135 ConfClass = new class Config();
136
137 // Start server
138 start(ServerName.c_str());
139 addExitHandler(this);
140}
141
142
143// Destructor: Free memory
144EvidenceServer::~EvidenceServer() {
145
146 Message(INFO, "Server stopped");
147
148 delete ConfClass;
149
150 int Ret;
151 if ((Ret = pthread_mutex_destroy(&Mutex)) != 0) {
152 Message(ERROR, "pthread_mutex_destroy() failed (%s)", strerror(Ret));
153 }
154
155 delete MessageService;
156 delete[] MessageData;
157}
158
159
160// DIM exit handler
161// Note handler runs in DIM thread, thus exit() will call destructors of static objects in this thread
162void EvidenceServer::exitHandler(int Code) {
163
164 Message(INFO, "Exit handler called (DIM exit code %d)", Code);
165 exit(EXIT_SUCCESS);
166}
167
168// DIM error handler
169void EvidenceServer::errorHandler(int Severity, int Code, char *Text) {
170
171 Message(ERROR, "%s (DIM error code %d, DIM severity %d)\n", Text, Code, Severity);
172}
173
174
175// Set server message (if Severity is FATAL, exit() will be invoked)
176void EvidenceServer::Message(int Severity, const char *Format, ...) {
177
178 static char ErrorString[] = "vasprintf() failed in Message()";
179 char Buffer[10];
180 char *Text;
181
182 // Assemble message from application
183 va_list ArgumentPointer;
184 va_start(ArgumentPointer, Format);
185 if (vasprintf(&Text, Format, ArgumentPointer) == -1) {
186 Text = ErrorString;
187 Severity = ERROR;
188 }
189 va_end(ArgumentPointer);
190
191 // Generate new Message structure and free text
192 struct Message *NewMsg = (struct Message *) new char [sizeof(struct Message)+strlen(Text)+1];
193 NewMsg->Severity = Severity;
194 strcpy(NewMsg->Text, Text);
195 if (Text != ErrorString) free(Text);
196
197 // Send message to console and log file
198 const char *Desc;
199 switch (Severity) {
200 case INFO: Desc = "Info"; break;
201 case WARN: Desc = "Warn"; break;
202 case ERROR: Desc = "Error"; break;
203 case FATAL: Desc = "Fatal"; break;
204 default: snprintf(Buffer, sizeof(Buffer), "%d", Severity);
205 Desc = Buffer;
206 }
207
208 printf("%s (%s): %s\n", MessageService->getName(), Desc, NewMsg->Text);
209 SendToLog("%s (%s): %s", MessageService->getName(), Desc, NewMsg->Text);
210
211 // Update DIM message service
212 if (MessageService != NULL) {
213 MessageService->updateService(NewMsg, sizeof(struct Message)+strlen(NewMsg->Text)+1);
214 }
215
216 // Delete old message
217 Lock();
218 delete[] MessageData;
219 MessageData = NewMsg;
220 Unlock();
221
222 // Terminate if severity if FATAL
223 if (Severity == FATAL) exit(EXIT_FAILURE);
224}
225
226
227// Send to central logging server with non-blocking command
228void EvidenceServer::SendToLog(const char *Format, ...) {
229
230 static char ErrorString[] = "vasprintf() failed in SendToLog()";
231 char *Buffer;
232 int Ret;
233
234 // Evaluate variable argument list
235 va_list ArgumentPointer;
236 va_start(ArgumentPointer, Format);
237 Ret = vasprintf(&Buffer, Format, ArgumentPointer);
238 va_end(ArgumentPointer);
239
240 // Send to logger
241 if (Ret != -1) {
242 DimClient::sendCommandNB("DColl/Log", Buffer);
243 free (Buffer);
244 }
245 else DimClient::sendCommandNB("DColl/Log", ErrorString);
246}
247
248
249// Get configuration data
250//
251// Program terminates if data is missing and no default given. Actual configuration
252// request will be made only if config file has modification since last request.
253// If called from infoHandler(), a non-blocking request will be made
254string EvidenceServer::GetConfig(string Item, string Default) {
255
256 string Result;
257 bool Blocking = false;
258
259 // If up-to-date data in configuration list available, return this
260 Lock();
261 if ((List.count(Item) > 0) && (List[Item].Time >= ConfClass->ConfigTimeStamp)) Result = List[Item].Value;
262 if (Threads.count(pthread_self()) != 0) Blocking = true;
263 Unlock();
264 if (!Result.empty()) return Result;
265
266 // Blocking configuration request
267 if (Blocking) {
268 DimRpcInfo Config((char *) "ConfigRequest", NO_LINK);
269 Config.setData((char *) (Name + " " + Item).c_str());
270
271 // Check if successful
272 if (!EvidenceServer::ServiceOK(&Config)) {
273 if (Default.empty()) {
274 Message(FATAL, "Configuration server unreachable, can't retrieve '%s'", Item.c_str());
275 }
276 else Result = Default;
277 }
278 else {
279 if (Config.getSize() <= 1) Result = Default;
280 else Result = string(Config.getString(), Config.getSize()-1); // Retrieve string safely
281 }
282
283 // Update configuration map
284 if (!Result.empty()) {
285 Lock();
286 List[Item].Value = Result;
287 List[Item].Time = ConfClass->ConfigTimeStamp;
288 Unlock();
289 }
290 }
291
292 // Non-blocking configuration request
293 if (!Blocking) {
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 EvidenceServer::Lock();
346 EvidenceServer::Threads.insert(pthread_self());
347 EvidenceServer::Unlock();
348
349 This->ConfigChanged();
350
351 EvidenceServer::Lock();
352 EvidenceServer::Threads.erase(pthread_self());
353 EvidenceServer::Unlock();
354}
355
356
357// Signal handler (causes pause() and other syscalls to return)
358void EvidenceServer::SignalHandler(int Signal) {
359
360 static int Count = 0;
361
362 // At first invocation just request exit
363 if (++Count == 1) {
364 This->ExitRequest = true;
365 return;
366 }
367
368 // If invoked twice, call exit()
369 if (Count == 2) {
370 This->Message(WARN, "Signal handler called twice, invoking exit() (signal %d)", Signal);
371 exit(EXIT_FAILURE);
372 }
373
374 // If invoked more than twice, call abort()
375 This->Message(WARN, "Signal handler called %d times, invoking abort(). Good bye. (signal %d)", Count, Signal);
376 abort();
377}
378
379
380// C++ exception handler (derived from gcc __verbose_terminate_handler())
381void EvidenceServer::Terminate() {
382
383 ostringstream Msg;
384 static bool Terminating = false;
385
386 if (Terminating) {
387 Msg << This->Name << ": Terminate() called recursively, calling abort()";
388 printf("%s\n", Msg.str().c_str());
389 This->SendToLog(Msg.str().c_str());
390 abort();
391 }
392
393 Terminating = true;
394
395 // Make sure there was an exception; terminate is also called for an
396 // attempt to rethrow when there is no suitable exception.
397 type_info *Type = abi::__cxa_current_exception_type();
398 if (Type != NULL) {
399 int Status = -1;
400 char *Demangled = NULL;
401
402 Demangled = abi::__cxa_demangle(Type->name(), 0, 0, &Status);
403 Msg << "Terminate() called after throwing an instance of '" << (Status==0 ? Demangled : Type->name()) << "'";
404 free(Demangled);
405
406 // If exception derived from std::exception, more information.
407 try { __throw_exception_again; }
408 catch (exception &E) {
409 Msg << " (what(): " << E.what() << ")";
410 }
411 catch (...) { }
412 }
413 else Msg << "Terminate() called without an active exception";
414
415 This->Message(FATAL, Msg.str().c_str());
416}
417
418
419// Translates DIM data safely to string (assures no invalid memory accesses are made)
420string EvidenceServer::ToString(char *Format, void *Data, int Size) {
421
422 ostringstream Text;
423
424 // 'Message' service format handled here
425 if (strcmp(Format, "I:1;C") == 0 && Size >= (int) sizeof(struct Message)) {
426 struct Message *Msg = (struct Message *) Data;
427 // Safely extract string and limit to length of C string (padding might make it longer)
428 string MsgText(Msg->Text, Size-sizeof(struct Message));
429 Text << Msg->Severity << " " << MsgText.erase(strlen(MsgText.c_str()));
430
431 return Text.str();
432 }
433
434 // Structure: print hex representation
435 if (strlen(Format) != 1) {
436 for (int i=0; i<Size; i++) {
437 Text << setw(2) << hex << *((char *) Data + i) << " ";
438 }
439 return Text.str();
440 }
441
442 // String if format "C" and terminated with \0
443 if (strcmp(Format, "C") == 0 && Size > 0 && *((char *) Data+Size-1)=='\0') {
444 return string((char *) Data);
445 }
446
447 // Number array
448 int ElementSize;
449 switch (*Format) {
450 case 'C': ElementSize = sizeof(char); break;
451 case 'I':
452 case 'L': ElementSize = sizeof(int); break;
453 case 'S': ElementSize = sizeof(short); break;
454 case 'F': ElementSize = sizeof(float); break;
455 case 'D': ElementSize = sizeof(double); break;
456 case 'X': ElementSize = sizeof(long long); break;
457 default: return string();
458 }
459
460 for (int i=0; i<Size/ElementSize; i++) {
461 // Space between entries
462 if (i != 0) Text << " ";
463
464 // Translate data
465 switch (*Format) {
466 case 'C': Text << *((char *) Data + i); break;
467 case 'I':
468 case 'L': Text << *((int *) Data + i); break;
469 case 'S': Text << *((short *) Data + i); break;
470 case 'F': Text << *((float *) Data + i); break;
471 case 'D': Text << *((double *) Data + i); break;
472 case 'X': Text << *((long long *) Data + i); break;
473 }
474 }
475
476 return Text.str();
477}
478
479
480// Checks if service contents indicates not available
481bool EvidenceServer::ServiceOK(DimInfo *Item) {
482
483 return !((Item->getSize() == strlen(NO_LINK)+1) &&
484 (memcmp(Item->getData(), NO_LINK, Item->getSize()) == 0));
485}
486
487bool EvidenceServer::ServiceOK(DimRpcInfo *Item) {
488
489 return !((Item->getSize() == strlen(NO_LINK)+1) &&
490 (memcmp(Item->getData(), NO_LINK, Item->getSize()) == 0));
491}
492
493bool EvidenceServer::ServiceOK(DimCurrentInfo *Item) {
494
495 return !((Item->getSize() == strlen(NO_LINK)+1) &&
496 (memcmp(Item->getData(), NO_LINK, Item->getSize()) == 0));
497}
498
499
500// Tokenize std::string using given delimeter list
501vector<string> EvidenceServer::Tokenize(const string &String, const string &Delimiters) {
502
503 vector<string> Tokens;
504 string::size_type Next, EndNext=0;
505
506 while (EndNext != string::npos) {
507 // Find next token
508 Next = String.find_first_not_of(Delimiters, EndNext);
509 EndNext = String.find_first_of(Delimiters, Next);
510
511 // Stop if end of string reached
512 if (Next == string::npos) break;
513
514 // Add token to vector.
515 Tokens.push_back(String.substr(Next, EndNext - Next));
516 }
517
518 return Tokens;
519}
520
521
522///////////////////////////
523// EvidenceHistory Class //
524///////////////////////////
525
526// Organisaztion of history buffer
527// F | T S D | T S D | 0 0 ...... | T S D | T S D | 0 -1
528//
529// F: Offset of oldest entry T: Time S: Size D: Data
530// F, T, S: int
531
532// Marker for history buffer
533const struct EvidenceHistory::Item EvidenceHistory::WrapMark = {0, -1, {}};
534const struct EvidenceHistory::Item EvidenceHistory::EndMark = {0, 0, {}};
535
536// Constructor
537EvidenceHistory::EvidenceHistory(std::string Name): Name(Name) {
538
539 Buffer = NULL;
540}
541
542// Destructor
543EvidenceHistory::~EvidenceHistory() {
544
545 delete[] Buffer;
546}
547
548// Requests service history (returns true if data received OK)
549bool EvidenceHistory::GetHistory() {
550
551 DimRpcInfo R((char *) "ServiceHistory", NO_LINK);
552 R.setData((char *) Name.c_str());
553
554 // Check if data OK
555 if (!EvidenceServer::ServiceOK(&R)) return false;
556 if (R.getSize() == 0) return false;
557
558 // Copy data to buffer
559 delete[] Buffer;
560 BufferSize = R.getSize();
561 Buffer = new char [BufferSize];
562
563 memcpy(Buffer, R.getData(), BufferSize);
564 DataStart = Buffer + strlen(Buffer) + 1;
565 Rewind();
566
567 return true;
568}
569
570// Returns next item in history buffer
571const struct EvidenceHistory::Item *EvidenceHistory::Next() {
572
573 if (Buffer == NULL) return NULL;
574
575 // Check for wrap around
576 if (memcmp(Pointer, &WrapMark, sizeof(WrapMark)) == 0) Pointer = (struct Item *) (DataStart + sizeof(int));
577 // Check if at end of ring buffer
578 if (memcmp(Pointer, &EndMark, sizeof(EndMark)) == 0) return NULL;
579
580 const struct Item *Ret = Pointer;
581 Pointer = (struct Item *) ((char *) (Ret + 1) + Ret->Size);
582
583 return Ret;
584}
585
586// Reset to start of buffer
587void EvidenceHistory::Rewind() {
588
589 if (Buffer != NULL) Pointer = (struct Item *) (DataStart + (*(int *) DataStart));
590}
591
592// Return DIM format string of service (NULL if no data)
593char *EvidenceHistory::GetFormat() {
594
595 return Buffer;
596}
Note: See TracBrowser for help on using the repository browser.