source: Evidence/Evidence.cc@ 241

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