source: Evidence/Evidence.cc@ 3220

Last change on this file since 3220 was 264, checked in by ogrimm, 14 years ago
Removed mutex locking in hvcontrol, use lockf() in Communicate()
File size: 16.4 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: 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;
61return;
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// Request configuration data possible only when answer to previous request received
88void EvidenceServer::Config::RequestNB(string Item) {
89
90 This->Lock();
91 if (CurrentItem.empty()) {
92 CurrentItem = Item;
93 setData(((char *) Item.c_str()));
94 }
95 This->Unlock();
96}
97
98
99//////////////////////////
100// EvidenceServer Class //
101//////////////////////////
102
103// Initialise
104EvidenceServer *EvidenceServer::This = NULL;
105pthread_mutex_t EvidenceServer::Mutex;
106set<pthread_t> EvidenceServer::Threads;
107
108
109// Constructor
110EvidenceServer::EvidenceServer(string ServerName): Name(ServerName) {
111
112 // Initialize
113 MessageService = NULL;
114 MessageData = NULL;
115 ExitRequest = false;
116 This = this;
117 Threads.insert(pthread_self());
118
119 // Initialise mutex
120 int Ret;
121 pthread_mutexattr_t Attr;
122
123 if ((Ret = pthread_mutexattr_settype(&Attr, PTHREAD_MUTEX_ERRORCHECK)) != 0) {
124 Message(FATAL, "pthread_mutex_settype() failed (%s)", strerror(Ret));
125 }
126 if ((Ret = pthread_mutex_init(&Mutex, &Attr)) != 0) {
127 Message(FATAL, "pthread_mutex_init() failed (%s)", strerror(Ret));
128 }
129
130 // Catch some signals
131 signal(SIGQUIT, &SignalHandler); // CTRL-Backspace
132 signal(SIGTERM, &SignalHandler); // Termination signal
133 signal(SIGINT, &SignalHandler); // CTRL-C
134 signal(SIGHUP, &SignalHandler); // Terminal closed
135
136 // Catch C++ unhandled exceptions
137 set_terminate(Terminate);
138
139 // Message service and initial message
140 MessageService = new DimService((Name+"/Message").c_str(), (char *) "I:1;C", NULL, 0);
141
142 string Rev(EVIDENCE_REVISION);
143 Message(INFO, "Server started (%s, compiled %s %s)", (Rev.substr(1, Rev.size()-3)).c_str(),__DATE__, __TIME__);
144
145 // Configuration class
146 ConfClass = new class Config();
147
148 // Start server
149 start(ServerName.c_str());
150 addExitHandler(this);
151}
152
153
154// Destructor: Free memory
155EvidenceServer::~EvidenceServer() {
156
157 Message(INFO, "Server stopped");
158
159 delete ConfClass;
160
161 int Ret;
162 if ((Ret = pthread_mutex_destroy(&Mutex)) != 0) {
163 Message(ERROR, "pthread_mutex_destroy() failed (%s)", strerror(Ret));
164 }
165
166 delete MessageService;
167 delete[] MessageData;
168}
169
170
171// DIM exit handler
172void EvidenceServer::exitHandler(int Code) {
173
174 Message(INFO, "Exit handler called (DIM exit code %d)", Code);
175 exit(EXIT_SUCCESS);
176}
177
178// DIM error handler
179void EvidenceServer::errorHandler(int Severity, int Code, char *Text) {
180
181 Message(ERROR, "%s (DIM error code %d, DIM severity %d)\n", Text, Code, Severity);
182}
183
184
185// Set server message (if Severity is FATAL, exit() will be invoked)
186void EvidenceServer::Message(MessageType Severity, const char *Format, ...) {
187
188 static const char* StateString[] = {"Info", "Warn", "Error", "Fatal"};
189 static char ErrorString[] = "vasprintf() failed in Message()";
190 char *Text;
191
192 // Assemble message from application
193 va_list ArgumentPointer;
194 va_start(ArgumentPointer, Format);
195 if (vasprintf(&Text, Format, ArgumentPointer) == -1) {
196 Text = ErrorString;
197 Severity = ERROR;
198 }
199 va_end(ArgumentPointer);
200
201 // Generate new Message structure and free text
202 struct Message *NewMsg = (struct Message *) new char [sizeof(struct Message)+strlen(Text)+1];
203 NewMsg->Severity = Severity;
204 strcpy(NewMsg->Text, Text);
205 if (Text != ErrorString) free(Text);
206
207 // Send message to console and log file
208 printf("%s (%s): %s\n", MessageService->getName(), StateString[Severity], NewMsg->Text);
209 SendToLog("%s (%s): %s", MessageService->getName(), StateString[Severity], 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 if (List.count(Item) > 0) {
296 ConfClass->RequestNB(Name + " " + Item);
297 Result = List[Item].Value;
298 }
299 Unlock();
300 }
301
302 // Terminate if no configuration information found
303 if (Result.empty()) Message(FATAL, "Missing configuration data '%s'", Item.c_str());
304
305 return Result;
306}
307
308
309// Locking and unlocking functions.
310// Message() is not used to avoid infinite recursion
311void EvidenceServer::Lock() {
312
313 int Ret;
314
315 if ((Ret = pthread_mutex_lock(&EvidenceServer::Mutex)) != 0) {
316 printf("pthread_mutex_lock() failed in Lock() (%s)", strerror(Ret));
317 SendToLog("pthread_mutex_lock() failed in Lock() (%s)", strerror(Ret));
318 exit(EXIT_FAILURE);
319 }
320}
321
322void EvidenceServer::Unlock() {
323
324 int Ret;
325
326 if ((Ret = pthread_mutex_unlock(&EvidenceServer::Mutex)) != 0) {
327 printf("pthread_mutex_unlock() failed in Unlock() (%s)", strerror(Ret));
328 SendToLog("pthread_mutex_unlock() failed in Unlock() (%s)", strerror(Ret));
329 exit(EXIT_FAILURE);
330 }
331}
332
333
334// ====== Static methods ======
335
336// Stub to call ConfigChanged() method of class as separate thread
337// Thread set is used to determine if blocking or non-blocking rpc is to be used
338void EvidenceServer::CallConfigChanged() {
339
340 EvidenceServer::Lock();
341 EvidenceServer::Threads.insert(pthread_self());
342 EvidenceServer::Unlock();
343
344 This->ConfigChanged();
345
346 EvidenceServer::Lock();
347 EvidenceServer::Threads.erase(pthread_self());
348 EvidenceServer::Unlock();
349}
350
351
352// Signal handler (causes pause() and other syscalls to return)
353void EvidenceServer::SignalHandler(int Signal) {
354
355 static bool Called = false;
356
357 // At first invocation just request exit
358 if (!Called) {
359 Called = true;
360 This->ExitRequest = true;
361 return;
362 }
363
364 // If invoked twice, call exit()
365 This->Message(WARN, "Signal handler called again, invoking exit() (signal %d)", Signal);
366 exit(EXIT_FAILURE);
367}
368
369
370// C++ exception handler (derived from gcc __verbose_terminate_handler())
371void EvidenceServer::Terminate() {
372
373 ostringstream Msg;
374 static bool Terminating = false;
375
376 if (Terminating) {
377 Msg << This->Name << ": Terminate() called recursively, calling abort()";
378 printf("%s\n", Msg.str().c_str());
379 This->SendToLog(Msg.str().c_str());
380 abort();
381 }
382
383 Terminating = true;
384
385 // Make sure there was an exception; terminate is also called for an
386 // attempt to rethrow when there is no suitable exception.
387 type_info *Type = abi::__cxa_current_exception_type();
388 if (Type != NULL) {
389 int Status = -1;
390 char *Demangled = NULL;
391
392 Demangled = abi::__cxa_demangle(Type->name(), 0, 0, &Status);
393 Msg << "Terminate() called after throwing an instance of '" << (Status==0 ? Demangled : Type->name()) << "'";
394 free(Demangled);
395
396 // If exception derived from std::exception, more information.
397 try { __throw_exception_again; }
398 catch (exception &E) {
399 Msg << " (what(): " << E.what() << ")";
400 }
401 catch (...) { }
402 }
403 else Msg << "Terminate() called without an active exception";
404
405 This->Message(FATAL, Msg.str().c_str());
406}
407
408
409// Translates DIM data safely to string (assures no invalid memory accesses are made)
410string EvidenceServer::ToString(char *Format, void *Data, int Size) {
411
412 ostringstream Text;
413
414 // 'Message' service format handled here
415 if (strcmp(Format, "I:1;C") == 0 && Size >= (int) sizeof(struct Message)) {
416 struct Message *Msg = (struct Message *) Data;
417 // Safely extract string and limit to length of C string (padding might make it longer)
418 string MsgText(Msg->Text, Size-sizeof(struct Message));
419 Text << Msg->Severity << " " << MsgText.erase(strlen(MsgText.c_str()));
420
421 return Text.str();
422 }
423
424 // Structure: print hex representation
425 if (strlen(Format) != 1) {
426 for (int i=0; i<Size; i++) {
427 Text << setw(2) << hex << *((char *) Data + i) << " ";
428 }
429 return Text.str();
430 }
431
432 // String if format "C" and terminated with \0
433 if (strcmp(Format, "C") == 0 && Size > 0 && *((char *) Data+Size-1)=='\0') {
434 return string((char *) Data);
435 }
436
437 // Number array
438 int ElementSize;
439 switch (*Format) {
440 case 'C': ElementSize = sizeof(char); break;
441 case 'I':
442 case 'L': ElementSize = sizeof(int); break;
443 case 'S': ElementSize = sizeof(short); break;
444 case 'F': ElementSize = sizeof(float); break;
445 case 'D': ElementSize = sizeof(double); break;
446 case 'X': ElementSize = sizeof(long long); break;
447 default: return string();
448 }
449
450 for (int i=0; i<Size/ElementSize; i++) {
451 // Space between entries
452 if (i != 0) Text << " ";
453
454 // Translate data
455 switch (*Format) {
456 case 'C': Text << *((char *) Data + i); break;
457 case 'I':
458 case 'L': Text << *((int *) Data + i); break;
459 case 'S': Text << *((short *) Data + i); break;
460 case 'F': Text << *((float *) Data + i); break;
461 case 'D': Text << *((double *) Data + i); break;
462 case 'X': Text << *((long long *) Data + i); break;
463 }
464 }
465
466 return Text.str();
467}
468
469
470// Checks if service contents indicates not available
471bool EvidenceServer::ServiceOK(DimInfo *Item) {
472
473 return !((Item->getSize() == strlen(NO_LINK)+1) &&
474 (memcmp(Item->getData(), NO_LINK, Item->getSize()) == 0));
475}
476
477bool EvidenceServer::ServiceOK(DimRpcInfo *Item) {
478
479 return !((Item->getSize() == strlen(NO_LINK)+1) &&
480 (memcmp(Item->getData(), NO_LINK, Item->getSize()) == 0));
481}
482
483bool EvidenceServer::ServiceOK(DimCurrentInfo *Item) {
484
485 return !((Item->getSize() == strlen(NO_LINK)+1) &&
486 (memcmp(Item->getData(), NO_LINK, Item->getSize()) == 0));
487}
488
489
490// Tokenize std::string using given delimeter list
491vector<string> EvidenceServer::Tokenize(const string &String, const string &Delimiters) {
492
493 vector<string> Tokens;
494 string::size_type Next, EndNext=0;
495
496 while (EndNext != string::npos) {
497 // Find next token
498 Next = String.find_first_not_of(Delimiters, EndNext);
499 EndNext = String.find_first_of(Delimiters, Next);
500
501 // Stop if end of string reached
502 if (Next == string::npos) break;
503
504 // Add token to vector.
505 Tokens.push_back(String.substr(Next, EndNext - Next));
506 }
507
508 return Tokens;
509}
510
511
512///////////////////////////
513// EvidenceHistory Class //
514///////////////////////////
515
516// Organisaztion of history buffer
517// F | T S D | T S D | 0 0 ...... | T S D | T S D | 0 -1
518//
519// F: Offset of oldest entry T: Time S: Size D: Data
520// F, T, S: int
521
522// Marker for history buffer
523const struct EvidenceHistory::Item EvidenceHistory::WrapMark = {0, -1, {}};
524const struct EvidenceHistory::Item EvidenceHistory::EndMark = {0, 0, {}};
525
526// Constructor
527EvidenceHistory::EvidenceHistory(std::string Name): Name(Name) {
528
529 Buffer = NULL;
530}
531
532// Destructor
533EvidenceHistory::~EvidenceHistory() {
534
535 delete[] Buffer;
536}
537
538// Requests service history (returns true if data received OK)
539bool EvidenceHistory::GetHistory() {
540
541 DimRpcInfo R((char *) "ServiceHistory", NO_LINK);
542 R.setData((char *) Name.c_str());
543
544 // Check if data OK
545 if (!EvidenceServer::ServiceOK(&R)) return false;
546 if (R.getSize() == 0) return false;
547
548 // Copy data to buffer
549 delete[] Buffer;
550 BufferSize = R.getSize();
551 Buffer = new char [BufferSize];
552
553 memcpy(Buffer, R.getData(), BufferSize);
554 DataStart = Buffer + strlen(Buffer) + 1;
555 Rewind();
556
557 return true;
558}
559
560// Returns next item in history buffer
561const struct EvidenceHistory::Item *EvidenceHistory::Next() {
562
563 if (Buffer == NULL) return NULL;
564
565 // Check for wrap around
566 if (memcmp(Pointer, &WrapMark, sizeof(WrapMark)) == 0) Pointer = (struct Item *) (DataStart + sizeof(int));
567 // Check if at end of ring buffer
568 if (memcmp(Pointer, &EndMark, sizeof(EndMark)) == 0) return NULL;
569
570 const struct Item *Ret = Pointer;
571 Pointer = (struct Item *) ((char *) (Ret + 1) + Ret->Size);
572
573 return Ret;
574}
575
576// Reset to start of buffer
577void EvidenceHistory::Rewind() {
578
579 if (Buffer != NULL) Pointer = (struct Item *) (DataStart + (*(int *) DataStart));
580}
581
582// Return DIM format string of service (NULL if no data)
583char *EvidenceHistory::GetFormat() {
584
585 return Buffer;
586}
Note: See TracBrowser for help on using the repository browser.