source: Evidence/Evidence.cc@ 254

Last change on this file since 254 was 253, checked in by ogrimm, 14 years ago
Added command ResetAlarm, Evidence servers now always safely translate a DIM string into a C string, added documentation, replaced several vectors my maps
File size: 16.2 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 This->Unlock();
125 }
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 Message(INFO, "Exit handler called (DIM exit code %d)", Code);
203 exit(EXIT_SUCCESS);
204}
205
206// DIM error handler
207void EvidenceServer::errorHandler(int Severity, int Code, char *Text) {
208
209 Message(ERROR, "%s (DIM error code %d, DIM severity %d)\n", Text, Code, Severity);
210}
211
212// Set server message (if Severity is FATAL, exit() will be invoked)
213void EvidenceServer::Message(MessageType Severity, const char *Format, ...) {
214
215 static const char* StateString[] = {"Info", "Warn", "Error", "Fatal"};
216 static char ErrorString[] = "vasprintf() failed in Message()";
217 char *Text;
218
219 // Assemble message from application
220 va_list ArgumentPointer;
221 va_start(ArgumentPointer, Format);
222 if (vasprintf(&Text, Format, ArgumentPointer) == -1) {
223 Text = ErrorString;
224 Severity = ERROR;
225 }
226 va_end(ArgumentPointer);
227
228 // Generate new Message structure and free text
229 struct Message *NewMsg = (struct Message *) new char [sizeof(struct Message)+strlen(Text)+1];
230 NewMsg->Severity = Severity;
231 strcpy(NewMsg->Text, Text);
232 if (Text != ErrorString) free(Text);
233
234 // Send message to console and log file
235 printf("%s (%s): %s\n", MessageService->getName(), StateString[Severity], NewMsg->Text);
236 SendToLog("%s (%s): %s", MessageService->getName(), StateString[Severity], NewMsg->Text);
237
238 // Update DIM message service
239 if (MessageService != NULL) {
240 MessageService->updateService(NewMsg, sizeof(struct Message)+strlen(NewMsg->Text)+1);
241 }
242
243 // Terminate if severity if FATAL
244 if (Severity == FATAL) exit(EXIT_FAILURE);
245
246 // Delete old message
247 // Note that Lock()/Unlock() might fail with a FATAL message. To avoid an infinite loop,
248 // check for FATAL severity is done before here.
249 Lock();
250 delete[] MessageData;
251 MessageData = NewMsg;
252 Unlock();
253}
254
255
256// Set to central logging server with non-blocking command (may be used in DIM handler)
257void EvidenceServer::SendToLog(const char *Format, ...) {
258
259 char *Buffer;
260 int Ret;
261
262 // Evaluate variable argument list
263 va_list ArgumentPointer;
264 va_start(ArgumentPointer, Format);
265 Ret = vasprintf(&Buffer, Format, ArgumentPointer);
266 va_end(ArgumentPointer);
267
268 // Send to logger
269 if (Ret != -1) {
270 DimClient::sendCommandNB("DColl/Log", Buffer);
271 free (Buffer);
272 }
273 else Message(ERROR, "Could not create logging text in SendToLog(), vasprintf() failed");
274}
275
276
277// Get configuration data
278//
279// Program terminates if data is missing and no default given. Actual configuration
280// request will be made only if config file has modification since last request.
281// If called from infoHandler(), a non-blocking request will be made
282string EvidenceServer::GetConfig(string Item, string Default) {
283
284 string Result = ConfClass->GetConfig(Item, Default);
285 if (Result.empty()) Message(FATAL, "Missing configuration data '%s'", Item.c_str());
286 return Result;
287}
288
289
290// Set signal emitted when configuraton file changes, signal handler calls ConfigChanged()
291void EvidenceServer::ActivateSignal(int Signal) {
292
293 struct sigaction S;
294
295 ConfigSignal = Signal;
296 S.sa_handler = &SignalHandler;
297 S.sa_flags = SA_RESTART;
298 sigaction(Signal, &S, NULL);
299}
300
301
302// Locking and unlocking for list access
303// Signal blocked before locking to avoid dead-lock by calling GetConfig() from ConfigChanged().
304void EvidenceServer::Lock() {
305
306 int Ret;
307 sigset_t Set;
308
309 if (ConfigSignal != 0) {
310 Ret = abs(sigemptyset(&Set));
311 Ret += abs(sigaddset(&Set, ConfigSignal));
312 Ret += abs(pthread_sigmask(SIG_BLOCK, &Set, NULL));
313
314 if (Ret != 0) Message(FATAL, "Signal masking failed in Lock()");
315 }
316
317 if ((Ret = pthread_mutex_lock(&Mutex)) != 0) {
318 Message(FATAL, "pthread_mutex_lock() failed in Lock() (%s)", strerror(Ret));
319 }
320}
321
322void EvidenceServer::Unlock() {
323
324 int Ret;
325 sigset_t Set;
326
327 if ((Ret = pthread_mutex_unlock(&Mutex)) != 0) {
328 Message(FATAL, "pthread_mutex_unlock() failed in Unlock() (%s)", strerror(Ret));
329 }
330
331 if (ConfigSignal != 0) {
332 Ret = abs(sigemptyset(&Set));
333 Ret += abs(sigaddset(&Set, ConfigSignal));
334 Ret += abs(pthread_sigmask(SIG_UNBLOCK, &Set, NULL));
335
336 if (Ret != 0) Message(FATAL, "Signal unmasking failed in Unlock()");
337 }
338}
339
340
341// ====== Static methods ======
342
343// Signal handler (causes pause() and other syscalls to return)
344void EvidenceServer::SignalHandler(int Signal) {
345
346 static bool Called = false;
347
348 // If signal indicates configuration change, invoke call-back
349 if (Signal == EvidenceServer::ConfigSignal) {
350 This->ConfigChanged();
351 return;
352 }
353
354 // At first invocation just request exit
355 if (!Called) {
356 Called = true;
357 This->ExitRequest = true;
358 return;
359 }
360
361 // If invoked twice, call exit()
362 This->Message(WARN, "Signal handler called again, invoking exit() (signal %d)", Signal);
363 exit(EXIT_FAILURE);
364}
365
366// C++ exception handler (derived from gcc __verbose_terminate_handler())
367void EvidenceServer::Terminate() {
368
369 ostringstream Msg;
370 static bool Terminating = false;
371
372 if (Terminating) {
373 Msg << This->Name << ": Terminate() called recursively, calling abort()";
374 printf("%s\n", Msg.str().c_str());
375 This->SendToLog(Msg.str().c_str());
376 abort();
377 }
378
379 Terminating = true;
380
381 // Make sure there was an exception; terminate is also called for an
382 // attempt to rethrow when there is no suitable exception.
383 type_info *Type = abi::__cxa_current_exception_type();
384 if (Type != NULL) {
385 int Status = -1;
386 char *Demangled = NULL;
387
388 Demangled = abi::__cxa_demangle(Type->name(), 0, 0, &Status);
389 Msg << "Terminate() called after throwing an instance of '" << (Status==0 ? Demangled : Type->name()) << "'";
390 free(Demangled);
391
392 // If exception derived from std::exception, more information.
393 try { __throw_exception_again; }
394 catch (exception &E) {
395 Msg << " (what(): " << E.what() << ")";
396 }
397 catch (...) { }
398 }
399 else Msg << "Terminate() called without an active exception";
400
401 This->Message(FATAL, Msg.str().c_str());
402}
403
404
405// Translates DIM data safely to string (assures no invalid memory accesses are made)
406string EvidenceServer::ToString(char *Format, void *Data, int Size) {
407
408 ostringstream Text;
409
410 // 'Message' service format handled here
411 if (strcmp(Format, "I:1;C") == 0 && Size >= (int) sizeof(struct Message)) {
412 struct Message *Msg = (struct Message *) Data;
413 // Safely extract string and limit to length of C string (padding might make it longer)
414 string MsgText(Msg->Text, Size-sizeof(struct Message));
415 Text << Msg->Severity << " " << MsgText.erase(strlen(MsgText.c_str()));
416
417 return Text.str();
418 }
419
420 // Structure: print hex representation
421 if (strlen(Format) != 1) {
422 for (int i=0; i<Size; i++) {
423 Text << setw(2) << hex << *((char *) Data + i) << " ";
424 }
425 return Text.str();
426 }
427
428 // String if format "C" and terminated with \0
429 if (strcmp(Format, "C") == 0 && Size > 0 && *((char *) Data+Size-1)=='\0') {
430 return string((char *) Data);
431 }
432
433 // Number array
434 int ElementSize;
435 switch (*Format) {
436 case 'C': ElementSize = sizeof(char); break;
437 case 'I':
438 case 'L': ElementSize = sizeof(int); break;
439 case 'S': ElementSize = sizeof(short); break;
440 case 'F': ElementSize = sizeof(float); break;
441 case 'D': ElementSize = sizeof(double); break;
442 case 'X': ElementSize = sizeof(long long); break;
443 default: return string();
444 }
445
446 for (int i=0; i<Size/ElementSize; i++) {
447 // Space between entries
448 if (i != 0) Text << " ";
449
450 // Translate data
451 switch (*Format) {
452 case 'C': Text << *((char *) Data + i); break;
453 case 'I':
454 case 'L': Text << *((int *) Data + i); break;
455 case 'S': Text << *((short *) Data + i); break;
456 case 'F': Text << *((float *) Data + i); break;
457 case 'D': Text << *((double *) Data + i); break;
458 case 'X': Text << *((long long *) Data + i); break;
459 }
460 }
461
462 return Text.str();
463}
464
465
466// Checks if service contents indicates not available
467bool EvidenceServer::ServiceOK(DimInfo *Item) {
468
469 return !((Item->getSize() == strlen(NO_LINK)+1) &&
470 (memcmp(Item->getData(), NO_LINK, Item->getSize()) == 0));
471}
472
473bool EvidenceServer::ServiceOK(DimRpcInfo *Item) {
474
475 return !((Item->getSize() == strlen(NO_LINK)+1) &&
476 (memcmp(Item->getData(), NO_LINK, Item->getSize()) == 0));
477}
478
479
480// Tokenize std::string using given delimeter list
481vector<string> EvidenceServer::Tokenize(const string &String, const string &Delimiters) {
482
483 vector<string> Tokens;
484 string::size_type Next, EndNext=0;
485
486 while (EndNext != string::npos) {
487 // Find next token
488 Next = String.find_first_not_of(Delimiters, EndNext);
489 EndNext = String.find_first_of(Delimiters, Next);
490
491 // Stop if end of string reached
492 if (Next == string::npos) break;
493
494 // Add token to vector.
495 Tokens.push_back(String.substr(Next, EndNext - Next));
496 }
497
498 return Tokens;
499}
500
501
502///////////////////////////
503// EvidenceHistory Class //
504///////////////////////////
505
506// Organisaztion of history buffer
507// F | T S D | T S D | 0 0 ...... | T S D | T S D | 0 -1
508//
509// F: Offset of oldest entry T: Time S: Size D: Data
510// F, T, S: int
511
512// Marker for history buffer
513const struct EvidenceHistory::Item EvidenceHistory::WrapMark = {0, -1, {}};
514const struct EvidenceHistory::Item EvidenceHistory::EndMark = {0, 0, {}};
515
516// Constructor
517EvidenceHistory::EvidenceHistory(std::string Name): Name(Name) {
518
519 Buffer = NULL;
520}
521
522// Destructor
523EvidenceHistory::~EvidenceHistory() {
524
525 delete[] Buffer;
526}
527
528// Requests service history (returns true if data received OK)
529bool EvidenceHistory::GetHistory() {
530
531 DimRpcInfo R((char *) "ServiceHistory", NO_LINK);
532 R.setData((char *) Name.c_str());
533
534 // Check if data OK
535 if (!EvidenceServer::ServiceOK(&R)) return false;
536 if (R.getSize() == 0) return false;
537
538 // Copy data to buffer
539 delete[] Buffer;
540 BufferSize = R.getSize();
541 Buffer = new char [BufferSize];
542
543 memcpy(Buffer, R.getData(), BufferSize);
544 DataStart = Buffer + strlen(Buffer) + 1;
545 Rewind();
546
547 return true;
548}
549
550// Returns next item in history buffer
551const struct EvidenceHistory::Item *EvidenceHistory::Next() {
552
553 if (Buffer == NULL) return NULL;
554
555 // Check for wrap around
556 if (memcmp(Pointer, &WrapMark, sizeof(WrapMark)) == 0) Pointer = (struct Item *) (DataStart + sizeof(int));
557 // Check if at end of ring buffer
558 if (memcmp(Pointer, &EndMark, sizeof(EndMark)) == 0) return NULL;
559
560 const struct Item *Ret = Pointer;
561 Pointer = (struct Item *) ((char *) (Ret + 1) + Ret->Size);
562
563 return Ret;
564}
565
566// Reset to start of buffer
567void EvidenceHistory::Rewind() {
568
569 if (Buffer != NULL) Pointer = (struct Item *) (DataStart + (*(int *) DataStart));
570}
571
572// Return DIM format string of service (NULL if no data)
573char *EvidenceHistory::GetFormat() {
574
575 return Buffer;
576}
Note: See TracBrowser for help on using the repository browser.