1 /*----------------------------------------------------------------------------
  2 Name:      CallbackServerUnparsed.c
  3 Project:   xmlBlaster.org
  4 Copyright: xmlBlaster.org, see xmlBlaster-LICENSE file
  5 Comment:   Establish a listen socket for xmlBlaster callbacks
  6 Author:    "Marcel Ruff" <xmlBlaster@marcelruff.info>
  7 Compile:
  8   LINUX:   gcc -g -Wall -DUSE_MAIN_CB -I.. -o CallbackServerUnparsed CallbackServerUnparsed.c xmlBlasterSocket.c ../util/msgUtil.c ../util/Properties.c
  9   WIN:     cl /MT -DUSE_MAIN_CB -D_WINDOWS -I.. CallbackServerUnparsed.c xmlBlasterSocket.c ../util/msgUtil.c ../util/Properties.c ws2_32.lib
 10   Solaris: cc -g -DUSE_MAIN_CB -I.. -o CallbackServerUnparsed CallbackServerUnparsed.c xmlBlasterSocket.c ../util/msgUtil.c ../util/Properties.c -lsocket -lnsl
 11 -----------------------------------------------------------------------------*/
 12 #include <stdio.h>
 13 #include <string.h>
 14 #if defined(WINCE)
 15 #  if defined(XB_USE_PTHREADS)
 16 #     include <pthreads/pthread.h>
 17 #  else
 18       /*#include <pthreads/need_errno.h> */
 19       static int errno=0; /* single threaded workaround*/
 20 #  endif
 21 #else
 22 #  include <errno.h>
 23 #endif
 24 #include <socket/xmlBlasterSocket.h> /* gethostname() */
 25 #include <CallbackServerUnparsed.h>
 26 #ifdef __IPhoneOS__
 27 #include <CoreFoundation/CFSocket.h>
 28 #include <XmlBlasterConnectionUnparsed.h>
 29 #endif
 30 static bool waitOnCallbackThreadAlive(CallbackServerUnparsed *cb, long millis);
 31 static bool waitOnCallbackThreadTermination(CallbackServerUnparsed *cb, long millis);
 32 static bool useThisSocket(CallbackServerUnparsed *cb, int socketToUse, int socketToUseUdp);
 33 static int runCallbackServer(CallbackServerUnparsed *cb);
 34 static bool createCallbackServer(CallbackServerUnparsed *cb);
 35 static bool isListening(CallbackServerUnparsed *cb);
 36 static bool readMessage(CallbackServerUnparsed *cb, SocketDataHolder *socketDataHolder, XmlBlasterException *exception, bool udp);
 37 static ssize_t writenPlain(void *cb, const int fd, const char *ptr, const size_t nbytes);
 38 static ssize_t readnPlain(void *cb, const int fd, char *ptr, const size_t nbytes, XmlBlasterNumReadFunc fpNumRead, void *userP2);
 39 static bool addResponseListener(CallbackServerUnparsed *cb, MsgRequestInfo *msgRequestInfoP, ResponseFp responseEventFp);
 40 static ResponseListener *removeResponseListener(CallbackServerUnparsed *cb, const char *requestId);
 41 static void voidSendResponse(CallbackServerUnparsed *cb, void *socketDataHolder, MsgUnitArr *msgUnitArr);
 42 static void sendResponse(CallbackServerUnparsed *cb, SocketDataHolder *socketDataHolder, MsgUnitArr *msgUnitArr);
 43 static void voidSendXmlBlasterException(CallbackServerUnparsed *cb, void *socketDataHolder, XmlBlasterException *exception);
 44 static void sendXmlBlasterException(CallbackServerUnparsed *cb, SocketDataHolder *socketDataHolder, XmlBlasterException *exception);
 45 static void voidSendResponseOrException(XMLBLASTER_C_bool success, CallbackServerUnparsed *cb, void *socketDataHolder, MsgUnitArr *msgUnitArrP, XmlBlasterException *exception);
 46 static void sendResponseOrException(XMLBLASTER_C_bool success, CallbackServerUnparsed *cb, SocketDataHolder *socketDataHolder, MsgUnitArr *msgUnitArrP, XmlBlasterException *exception);
 47 static void shutdownCallbackServer(CallbackServerUnparsed *cb);
 48 static void closeAcceptSocket(CallbackServerUnparsed *cb);
 49 
 50 /*
 51 static void xmlBlasterNumRead_test(void *xb, const size_t currBytesRead, const size_t nbytes) {
 52    printf("xmlBlasterSocket.c: DEUBG ONLY currBytesRead=%ld nbytes=%ld\n", (long)currBytesRead, (long)nbytes);
 53 }
 54 */
 55 
 56 
 57 /**
 58  * See header for a description.
 59  */
 60 CallbackServerUnparsed *getCallbackServerUnparsed(int argc, const char* const* argv,
 61                         UpdateCbFp updateCb, void *updateCbUserData)
 62 {
 63    CallbackServerUnparsed *cb = (CallbackServerUnparsed *)calloc(1,
 64                                 sizeof(CallbackServerUnparsed));
 65    if (cb == 0) return cb;
 66    cb->props = createProperties(argc, argv);
 67    if (cb->props == 0) {
 68       freeCallbackServerUnparsed(&cb);
 69       return (CallbackServerUnparsed *)0;
 70    }
 71    cb->stopListenLoop = false;
 72    cb->listenSocket = -1; /* Can be reused from XmlBlasterConnectionUnparsed */
 73    cb->acceptSocket = -1; /* Can be reused from XmlBlasterConnectionUnparsed */
 74    cb->socketUdp = -1; /* Can be reused from XmlBlasterConnectionUnparsed */
 75    cb->useThisSocket = useThisSocket;
 76    cb->runCallbackServer = runCallbackServer;
 77    cb->isListening = isListening;
 78    cb->shutdown = shutdownCallbackServer;
 79    cb->reusingConnectionSocket = false; /* is true if we tunnel callback through the client connection socket */
 80    cb->logLevel = parseLogLevel(cb->props->getString(cb->props, "logLevel", "WARN"));
 81    cb->log = xmlBlasterDefaultLogging;
 82    cb->logUserP = 0;
 83    cb->waitOnCallbackThreadAlive = waitOnCallbackThreadAlive;
 84    cb->waitOnCallbackThreadTermination = waitOnCallbackThreadTermination;
 85    cb->hostCB = strcpyAlloc(cb->props->getString(cb->props, "dispatch/callback/plugin/socket/hostname", 0));
 86    cb->portCB = cb->props->getInt(cb->props, "dispatch/callback/plugin/socket/port", DEFAULT_CALLBACK_SERVER_PORT);
 87    cb->updateCb = updateCb;
 88    cb->updateCbUserData = updateCbUserData; /* A optional pointer from the client code which is returned to the update() function call */
 89    memset(cb->responseListener, 0, MAX_RESPONSE_LISTENER_SIZE*sizeof(ResponseListener));
 90    pthread_mutex_init(&cb->responseListenerMutex, NULL); /* returns always 0 */
 91    cb->addResponseListener = addResponseListener;
 92    cb->removeResponseListener = removeResponseListener;
 93    cb->_threadIsAliveOnce = true;
 94    cb->threadIsAlive = false;
 95    cb->sendResponse = voidSendResponse;
 96    cb->sendXmlBlasterException = voidSendXmlBlasterException;
 97    cb->sendResponseOrException = voidSendResponseOrException;
 98 
 99    cb->writeToSocket.writeToSocketFuncP = writenPlain;
100    cb->writeToSocket.userP = cb;
101 
102    cb->readFromSocket.readFromSocketFuncP = readnPlain;
103    cb->readFromSocket.userP = cb;
104    cb->readFromSocket.numReadFuncP = 0; /* xmlBlasterNumRead_test */
105    cb->readFromSocket.numReadUserP = 0;
106    return cb;
107 }
108 
109 /*
110  * @see header
111  */
112 bool useThisSocket(CallbackServerUnparsed *cb, int socketToUse, int socketToUseUdp)
113 {
114 #ifdef __IPhoneOS__
115    #  pragma unused(fd) /*if (socketToUse < 200) printf("CallbackServerUparsed.c: dummy printf to avoid compiler warning\n");*/
116    cb->portCB = 12345;
117    strcpyRealloc(&cb->hostCB, "127.0.0.1"); /* inet_ntoa holds the host in an internal static string */
118    /*
119    cb->listenSocket = CFSocketGetNative(globalIPhoneXb->cfSocketRef);
120 
121    cb->acceptSocket = CFSocketGetNative(globalIPhoneXb->cfSocketRef);
122    */
123    cb->listenSocket = 0;
124 
125    cb->acceptSocket = 0;
126 
127    cb->socketUdp = socketToUseUdp;
128    cb->reusingConnectionSocket = true; /* we tunnel callback through the client connection socket */
129 #else
130    struct sockaddr_in localAddr;
131    socklen_t size = (socklen_t)sizeof(localAddr);
132    memset((char *)&localAddr, 0, (size_t)size);
133    if (getsockname(socketToUse, (struct sockaddr *)&localAddr, &size) == -1) {
134       if (cb->logLevel>=XMLBLASTER_LOG_WARN) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_WARN, __FILE__,
135          "Can't determine the local socket host and port, errno=%d", errno);
136       return false;
137    }
138    cb->portCB = (int)ntohs(localAddr.sin_port);
139    strcpyRealloc(&cb->hostCB, inet_ntoa(localAddr.sin_addr)); /* inet_ntoa holds the host in an internal static string */
140 
141    cb->listenSocket = socketToUse;
142    cb->acceptSocket = socketToUse;
143    cb->socketUdp = socketToUseUdp;
144    cb->reusingConnectionSocket = true; /* we tunnel callback through the client connection socket */
145 
146    if (cb->logLevel>=XMLBLASTER_LOG_INFO) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_INFO, __FILE__,
147       "Forced callback server to reuse socket descriptor '%d' on localHostname=%s localPort=%d",
148                         socketToUse, cb->hostCB, cb->portCB);
149 #endif
150    return true;
151 }
152 
153 /**
154  * Wait after pthread_create() until thread is running.
155  * @return false if not alive after given millis
156  */
157 static bool waitOnCallbackThreadAlive(CallbackServerUnparsed *cb, long millis) {
158    int i;
159    const int count = 100;
160    const int milliStep = (millis < count) ? 1 : (int)(millis / count);
161    for(i=0; i<count; i++) {
162       if (cb->_threadIsAliveOnce) {
163         return true;
164       }
165       if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
166           "waitOnCallbackThreadAlive(i=%d/%d) waiting %d millis ...", i, count, milliStep);
167       sleepMillis(milliStep);
168    }
169    cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_ERROR, __FILE__,
170          "waitOnCallbackThreadAlive() failed after %d milliseconds, thread has never reached alive", millis);
171    return false;
172 }
173 
174 /**
175  * For pthread_detached operation only (does not make sense in pthread_join() mode).
176  * @return false if not terminated after given millis
177  */
178 static bool waitOnCallbackThreadTermination(CallbackServerUnparsed *cb, long millis) {
179    int i;
180    const int count = 100;
181    const int milliStep = (millis < count) ? 1 : (int)(millis / count);
182    for(i=0; i<count; i++) {
183       if (!cb->threadIsAlive) {
184         return true;
185       }
186       if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
187           "waitOnCallbackThreadTermination(i=%d/%d) waiting %d millis ...", i, count, milliStep);
188       sleepMillis(milliStep);
189    }
190    cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_ERROR, __FILE__,
191          "waitOnCallbackThreadTermination() failed after %d milliseconds, thread has not terminated, it seems to block on the socket", millis);
192    return false;
193 }
194 
195 void freeCallbackServerUnparsed(CallbackServerUnparsed **cb_)
196 {
197    CallbackServerUnparsed *cb = *cb_;
198    if (cb != 0) {
199       bool hasTerminated;
200       shutdownCallbackServer(cb);
201       hasTerminated = cb->waitOnCallbackThreadTermination(cb, 5000);
202       freeProperties(cb->props);
203       if (hasTerminated) {
204          pthread_mutex_destroy(&cb->responseListenerMutex);
205          free(cb); /* Prefer to have a leak instead of a crash */
206       }
207       *cb_ = 0;
208    }
209 }
210 
211 /**
212  * Write uncompressed to socket (not thread safe)
213  */
214 static ssize_t writenPlain(void *userP, const int fd, const char *ptr, const size_t nbytes) {
215    if (userP) userP = 0; /* To avoid compiler warning */
216    return writen(fd, ptr, nbytes);
217 }
218 
219 /**
220  * Read data from socket, uncompress data if needed (not thread safe)
221  */
222 static ssize_t readnPlain(void * userP, const int fd, char *ptr, const size_t nbytes, XmlBlasterNumReadFunc fpNumRead, void *userP2) {
223    if (userP) userP = 0; /* To avoid compiler warning */
224    return readn(fd, ptr, nbytes, fpNumRead, userP2);
225 }
226 
227 static int responseListenerMutexLock(CallbackServerUnparsed *cb) {
228    int retInt = 0;
229    if ((retInt = pthread_mutex_lock(&cb->responseListenerMutex)) != 0) {
230       char p[XMLBLASTEREXCEPTION_MESSAGE_LEN];
231       SNPRINTF(p, XMLBLASTEREXCEPTION_MESSAGE_LEN,
232                "[%.100s:%d] Error trying to lock cbMutex %d", __FILE__, __LINE__, retInt);
233       cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_ERROR, __FILE__, p);
234    }
235    return retInt;
236 }
237 
238 #if defined(_WINDOWS)
239 static char *strerror_r(int retInt, char * errnoStr, size_t size) {
240    /*int ret = */strerror_s(errnoStr, size, retInt);
241    return errnoStr;
242 }
243 #endif
244 
245 static int responseListenerMutexUnLock(CallbackServerUnparsed *cb) {
246    int retInt = 0;
247    if ((retInt = pthread_mutex_unlock(&cb->responseListenerMutex)) != 0) {
248       char p[XMLBLASTEREXCEPTION_MESSAGE_LEN];
249       char errnoStr[XMLBLASTEREXCEPTION_MESSAGE_LEN];
250       strerror_r(retInt, errnoStr, XMLBLASTEREXCEPTION_MESSAGE_LEN);
251       SNPRINTF(p, XMLBLASTEREXCEPTION_MESSAGE_LEN,
252                "[%.100s:%d] Error trying to unlock cbMutex %d = %s", __FILE__, __LINE__, retInt, errnoStr);
253       cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_ERROR, __FILE__, p);
254    }
255    return retInt;
256 }
257 
258 static bool addResponseListener(CallbackServerUnparsed *cb, MsgRequestInfo *msgRequestInfoP, ResponseFp responseEventFp) {
259    int i;
260    if (responseEventFp == 0) {
261       return false;
262    }
263    responseListenerMutexLock(cb);
264    for (i=0; i<MAX_RESPONSE_LISTENER_SIZE; i++) {
265       if (cb->responseListener[i].msgRequestInfoP == 0) {
266          cb->responseListener[i].msgRequestInfoP = msgRequestInfoP;
267          cb->responseListener[i].responseEventFp = responseEventFp;
268          if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
269             "addResponseListener(i=%d, requestId=%s)", i, msgRequestInfoP->requestIdStr);
270          responseListenerMutexUnLock(cb);
271          return true;
272       }
273    }
274    responseListenerMutexUnLock(cb);
275    cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_ERROR, __FILE__,
276       "PANIC too many requests (%d) are waiting for a response, you are not registered", MAX_RESPONSE_LISTENER_SIZE);
277    return false;
278 }
279 
280 static ResponseListener *getResponseListener(CallbackServerUnparsed *cb, const char *requestId) {
281    int i;
282    if (requestId == 0) {
283       return 0;
284    }
285    responseListenerMutexLock(cb);
286    for (i=0; i<MAX_RESPONSE_LISTENER_SIZE; i++) {
287       const MsgRequestInfo * const pp = cb->responseListener[i].msgRequestInfoP;
288       if (pp == 0) {
289          continue;
290       }
291       if (!strcmp(pp->requestIdStr, requestId)) {
292          responseListenerMutexUnLock(cb);
293          return &cb->responseListener[i];
294       }
295    }
296    responseListenerMutexUnLock(cb);
297    cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_ERROR, __FILE__, "RequestId '%s' is not registered", requestId);
298    return 0;
299 }
300 
301 static ResponseListener *removeResponseListener(CallbackServerUnparsed *cb, const char *requestId) {
302    int i;
303    responseListenerMutexLock(cb);
304    for (i=0; i<MAX_RESPONSE_LISTENER_SIZE; i++) {
305       const MsgRequestInfo * const pp = cb->responseListener[i].msgRequestInfoP;
306       if (pp == 0) {
307          continue;
308       }
309       if (!strcmp(pp->requestIdStr, requestId)) {
310          cb->responseListener[i].msgRequestInfoP = 0;
311          responseListenerMutexUnLock(cb);
312          return &cb->responseListener[i];
313       }
314    }
315    responseListenerMutexUnLock(cb);
316    cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_ERROR, __FILE__, "Can't remove requestId '%s', requestId is not registered", requestId);
317    return (ResponseListener *)0;
318 }
319 
320 /**
321  * Called by listenLoop when a new message has arrived.
322  */
323 static void handleMessage(CallbackServerUnparsed *cb, SocketDataHolder* socketDataHolder, XmlBlasterException* xmlBlasterException, bool success) {
324 
325    MsgUnitArr *msgUnitArrP;
326 
327    if (success == false) { /* EOF */
328       int i;
329       if (!cb->reusingConnectionSocket)
330          cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_WARN, __FILE__, "Lost callback socket connection to xmlBlaster (EOF)");
331       closeAcceptSocket(cb);
332       /* Notify pending requests, otherwise they block in their mutex for a minute ... */
333       for (i=0; i<MAX_RESPONSE_LISTENER_SIZE; i++) {
334          XmlBlasterException exception;
335          ResponseListener *listener;
336          MsgRequestInfo *msgRequestInfoP;
337 
338          responseListenerMutexLock(cb);
339          listener = &cb->responseListener[i];
340          if (listener->msgRequestInfoP == 0) {
341             responseListenerMutexUnLock(cb);
342             continue;
343          }
344          /* Handle waiting MSG_TYPE_INVOKE threads (oneways are not in this list) */
345          msgRequestInfoP = listener->msgRequestInfoP;
346          cb->responseListener[i].msgRequestInfoP = 0;
347          responseListenerMutexUnLock(cb);
348 
349          initializeXmlBlasterException(&exception);
350 
351          /* Simulate an exception on client side ... */
352          socketDataHolder->type = (char)MSG_TYPE_EXCEPTION;
353          strncpy0(socketDataHolder->requestId, msgRequestInfoP->requestIdStr, MAX_REQUESTID_LEN);
354          strncpy0(socketDataHolder->methodName, msgRequestInfoP->methodName, MAX_METHODNAME_LEN);
355 
356          exception.remote = true;
357          strncpy0(exception.errorCode, "communication.noConnection", XMLBLASTEREXCEPTION_ERRORCODE_LEN);
358          SNPRINTF(exception.message, XMLBLASTEREXCEPTION_MESSAGE_LEN,
359                "[%.100s:%d] Lost connection to xmlBlaster with server side EOF", __FILE__, __LINE__);
360 
361          encodeXmlBlasterException(&socketDataHolder->blob, &exception, false);
362 
363          /* Takes a clone of socketDataHolder->blob */
364          listener->responseEventFp(msgRequestInfoP, socketDataHolder);
365          /* Now the client thread has wakened up and returns:
366           * msgRequestInfoP is invalid now as it was on client thread stack
367           */
368 
369          freeBlobHolderContent(&socketDataHolder->blob);
370          if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
371             "Notified pending requestId '%s' about lost socket connection", socketDataHolder->requestId);
372       }
373       return;
374    }
375 
376    if (*xmlBlasterException->errorCode != 0) {
377       cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_ERROR, __FILE__,
378          "Couldn't read message from xmlBlaster: errorCode=%s message=%s",
379                   xmlBlasterException->errorCode, xmlBlasterException->message);
380       return;
381    }
382 
383    if (cb->reusingConnectionSocket &&
384          (socketDataHolder->type == (char)MSG_TYPE_RESPONSE || socketDataHolder->type == (char)MSG_TYPE_EXCEPTION)) {
385       ResponseListener *listener = getResponseListener(cb, socketDataHolder->requestId);
386       if (listener != 0) {
387          /* This is a response for a request (no callback for us) */
388          MsgRequestInfo *msgRequestInfoP = listener->msgRequestInfoP;
389          removeResponseListener(cb, socketDataHolder->requestId);
390          listener->responseEventFp(msgRequestInfoP, socketDataHolder);
391          freeBlobHolderContent(&socketDataHolder->blob);
392          if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
393             "Forwarded message with requestId '%s' to response listener", socketDataHolder->requestId);
394          return;
395       }
396       else {
397          cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_ERROR, __FILE__,
398             "PANIC: Did not expect an INVOCATION '%c'='%d' as a callback",
399                   socketDataHolder->type, (int)socketDataHolder->type);
400          freeBlobHolderContent(&socketDataHolder->blob);
401          return;
402       }
403    }
404 
405    msgUnitArrP = parseMsgUnitArr(socketDataHolder->blob.dataLen, socketDataHolder->blob.data);
406    freeBlobHolderContent(&(socketDataHolder->blob));
407 
408    if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
409       "Received requestId '%s' callback %s()",
410       socketDataHolder->requestId, socketDataHolder->methodName);
411 
412    if (strcmp(socketDataHolder->methodName, XMLBLASTER_PING) == 0) {
413       size_t i;
414       for (i=0; i<msgUnitArrP->len; i++) {
415          msgUnitArrP->msgUnitArr[i].responseQos = strcpyAlloc("<qos/>");
416       }
417       sendResponse(cb, socketDataHolder, msgUnitArrP);
418       freeMsgUnitArr(msgUnitArrP);
419    }
420    else if (strcmp(socketDataHolder->methodName, XMLBLASTER_UPDATE) == 0 ||
421             strcmp(socketDataHolder->methodName, XMLBLASTER_UPDATE_ONEWAY) == 0) {
422       if (cb->updateCb != 0) { /* Client has registered to receive callback messages? */
423          if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
424             "Calling client %s() for requestId '%s' ...",
425             socketDataHolder->methodName, socketDataHolder->requestId);
426 
427          strncpy0(msgUnitArrP->secretSessionId, socketDataHolder->secretSessionId, MAX_SESSIONID_LEN);
428          msgUnitArrP->isOneway = (strcmp(socketDataHolder->methodName, XMLBLASTER_UPDATE_ONEWAY) == 0);
429          cb->updateCb(msgUnitArrP, cb, xmlBlasterException, socketDataHolder);
430       }
431       else { /* Unexpected update arrived, the client was not interested, see similar behavior in XmlBlasterAccess.java:update() */
432          size_t i;
433          for (i=0; i<msgUnitArrP->len; i++) {
434             msgUnitArrP->msgUnitArr[i].responseQos = strcpyAlloc("<qos><state id='OK'/></qos>");
435             cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_ERROR, __FILE__,
436                "Ignoring unexpected %s() message as client has not registered a callback, requestId is '%s' ...",
437                socketDataHolder->methodName, socketDataHolder->requestId);
438          }
439          sendResponseOrException(true, cb, socketDataHolder, msgUnitArrP, xmlBlasterException);
440       }
441    }
442    else {
443       cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_ERROR, __FILE__,
444       "Received unknown callback methodName=%s", socketDataHolder->methodName);
445    }
446 
447 }
448 
449 
450 /**
451  * The run method of the two threads (TCP or UDP).
452  * <p />
453  * The caller must do a pthread_join or pthread_detach to avoid leaking,
454  * <br />
455  * see freeXmlBlasterAccessUnparsed() for TCP
456  * or runCallbackServer() for UDP
457  * <p />
458  * Set cb->stopListenLoop to false to end the thread
459  */
460 static int listenLoop(ListenLoopArgs* ls)
461 {
462    int rc;
463    CallbackServerUnparsed *cb = ls->cb;
464    bool udp = ls->udp;
465    XmlBlasterException xmlBlasterException;
466    SocketDataHolder socketDataHolder;
467    bool success;
468    bool useUdpForOneway = cb->socketUdp != -1;
469    for(;;) {
470       memset(&xmlBlasterException, 0, sizeof(XmlBlasterException));
471       /* Here we block until a message arrives, see parseSocketData() */
472       if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
473          "Going to block on socket read until a new message arrives ...");
474       if (cb->stopListenLoop) break;
475       memset(&socketDataHolder, 0, sizeof(SocketDataHolder));
476       success = readMessage(cb, &socketDataHolder, &xmlBlasterException, udp);
477       if (cb->stopListenLoop) {
478         freeBlobHolderContent(&socketDataHolder.blob);
479         break;
480       }
481       cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__, "%s arrived, success=%s", udp ? "UDP" : "TCP", success ? "true" : "false -> EOF");
482 
483       if (useUdpForOneway) {
484          rc = pthread_mutex_lock(&cb->listenMutex);
485          if (rc != 0) /* EINVAL */
486             cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_ERROR, __FILE__, "pthread_mutex_lock() returned %d.", rc);
487       }
488 
489       handleMessage(cb, &socketDataHolder, &xmlBlasterException, success);
490 
491       if (useUdpForOneway) {
492          rc = pthread_mutex_unlock(&cb->listenMutex);
493          if (rc != 0) /* EPERM */
494             cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_ERROR, __FILE__, "pthread_mutex_unlock() returned %d.", rc);
495       }
496 
497       if (cb->stopListenLoop || !success)
498          break;
499    }
500    /*pthread_exit(NULL);*/
501    return 0;
502 }
503 
504 
505 /**
506  * Started by XmlBlasterAccessUnparsed.c pthread_create(runCallbackServer).
507  * <p />
508  * Open a socket and only leaves when the connection is lost (on EOF),
509  * in this case implicit pthread_exit() is called.
510  * <p />
511  * xmlBlaster will connect and receive callback messages.
512  * @return 0 on success, 1 on error. The return value is the exit value returned by pthread_join()
513  */
514 static int runCallbackServer(CallbackServerUnparsed *cb)
515 {
516    int rc;
517    int retVal = 0;
518 
519    const bool useUdpForOneway = cb->socketUdp != -1;
520 
521    cb->threadIsAlive = true;
522    cb->_threadIsAliveOnce = true;
523 
524    if (cb->listenSocket == -1) {
525       if (createCallbackServer(cb) == false) {
526          cb->threadIsAlive = false;
527          return 1;
528       }
529    }
530    else {
531       if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
532          "Reusing connection socket to tunnel callback messages");
533    }
534 
535    if (useUdpForOneway) {
536       ListenLoopArgs* tcpLoop = 0;
537       ListenLoopArgs* udpLoop = 0;
538 
539       /* We need to create two threads: one for TCP and one for the UDP callback listener */
540       pthread_t tcpListenThread, udpListenThread;
541 
542       rc = pthread_mutex_init(&cb->listenMutex, NULL); /* rc is always 0 */
543 
544       tcpLoop = (ListenLoopArgs*)malloc(sizeof(ListenLoopArgs)); tcpLoop->cb = cb; tcpLoop->udp = false;
545       rc = pthread_create(&tcpListenThread, NULL, (void * (*)(void *))listenLoop, tcpLoop);
546 
547       udpLoop = (ListenLoopArgs*)malloc(sizeof(ListenLoopArgs)); udpLoop->cb = cb; udpLoop->udp = true;
548       rc = pthread_create(&udpListenThread, NULL, (void * (*)(void *))listenLoop, udpLoop);
549 
550       if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
551             "Waiting to join tcpListenThread ...");
552       pthread_join(tcpListenThread, NULL);
553       free(tcpLoop);
554 
555       if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
556             "Waiting to join udpListenThread ...");
557       pthread_join(udpListenThread, NULL);
558       free(udpLoop);
559 
560       rc = pthread_mutex_destroy(&cb->listenMutex);
561       if (rc != 0) /* EBUSY */
562          cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_ERROR, __FILE__, "pthread_mutex_destroy() returned %d, we ignore it", rc);
563    }
564    else {
565       /* TCP only: no separate thread is needed (is called from XmlBlasterAccessUnparsed:pthread_create) */
566       ListenLoopArgs tcpLoop; tcpLoop.cb = cb; tcpLoop.udp = false;
567       retVal = listenLoop(&tcpLoop);
568    }
569 
570    if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
571          "Callbackserver thread is dying now ...");
572    cb->threadIsAlive = false; /* cb can be freed now */
573    return retVal;
574 }
575 
576 /**
577  * Called from separate thread.
578  * Is only called if we start a dedicated callback server (not tunneling
579  * through the connection socket).
580  * @return true The callback server is started, false on error
581  */
582 static bool createCallbackServer(CallbackServerUnparsed *cb)
583 {
584    socklen_t cli_len;
585    struct hostent hostbuf, *hostP = NULL;
586    struct sockaddr_in serv_addr, cli_addr;
587    char *tmphstbuf=NULL;
588    size_t hstbuflen=0;
589    char serverHostName[256];
590    char errP[MAX_ERRNO_LEN];
591    if (cb->hostCB == 0) {
592       strcpyRealloc(&cb->hostCB, "localhost");
593       if (gethostname(serverHostName, 125) == 0)
594          strcpyRealloc(&cb->hostCB, serverHostName);
595    }
596 
597    if (cb->logLevel>=XMLBLASTER_LOG_INFO) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_INFO, __FILE__,
598       "Starting callback server -dispatch/callback/plugin/socket/hostname %s -dispatch/callback/plugin/socket/port %d ...",
599                cb->hostCB, cb->portCB);
600 
601    /*
602       * Get a socket to work with.
603       */
604    if ((cb->listenSocket = (int)socket(AF_INET, SOCK_STREAM, 0)) < 0) {
605       if (cb->logLevel>=XMLBLASTER_LOG_WARN) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_WARN, __FILE__,
606          "Failed creating socket for callback server -dispatch/callback/plugin/socket/hostname %s -dispatch/callback/plugin/socket/port %d",
607             cb->hostCB, cb->portCB);
608          cb->threadIsAlive = false;
609          return false;
610    }
611 
612    /*
613     * Create the address we will be binding to.
614     */
615    serv_addr.sin_family = AF_INET;
616    *errP = 0;
617    hostP = gethostbyname_re(cb->hostCB, &hostbuf, &tmphstbuf, &hstbuflen, errP);
618 
619    if (*errP != 0) {
620       char message[EXCEPTIONSTRUCT_MESSAGE_LEN];
621       SNPRINTF(message, XMLBLASTEREXCEPTION_MESSAGE_LEN,
622                "[%.100s:%d] Lookup xmlBlaster failed, %s",
623                __FILE__, __LINE__, errP);
624       cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_WARN, __FILE__, message);
625       *errP = 0;
626    }
627 
628    if (hostP != NULL) {
629       serv_addr.sin_addr.s_addr = ((struct in_addr *)(hostP->h_addr))->s_addr; /*inet_addr("192.168.1.2"); */
630       free(tmphstbuf);
631    }
632    else
633       serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
634    serv_addr.sin_port = htons((u_short)cb->portCB);
635 
636    if (bind(cb->listenSocket, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
637       if (cb->logLevel>=XMLBLASTER_LOG_WARN) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_WARN, __FILE__,
638          "Failed binding port for callback server -dispatch/callback/plugin/socket/hostname %s -dispatch/callback/plugin/socket/port %d",
639             cb->hostCB, cb->portCB);
640       return false;
641    }
642 
643    /*
644       * Listen on the socket.
645       */
646    if (listen(cb->listenSocket, 5) < 0) {
647       if (cb->logLevel>=XMLBLASTER_LOG_WARN) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_WARN, __FILE__,
648          "Failed creating listener for callback server -dispatch/callback/plugin/socket/hostname %s -dispatch/callback/plugin/socket/port %d",
649             cb->hostCB, cb->portCB);
650       return false;
651    }
652 
653    if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
654       "[CallbackServerUnparsed] Waiting for xmlBlaster to connect ...");
655 
656    /*
657       * Accept connections.  When we accept one, ns
658       * will be connected to the client.  cli_addr will
659       * contain the address of the client.
660       */
661    cli_len = (socklen_t)sizeof(cli_addr);
662    if ((cb->acceptSocket = (int)accept(cb->listenSocket, (struct sockaddr *)&cli_addr, &cli_len)) < 0) {
663       if (cb->logLevel>=XMLBLASTER_LOG_ERROR) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_ERROR, __FILE__,
664          "[CallbackServerUnparsed] accept failed");
665          cb->threadIsAlive = false;
666          return false;
667    }
668    if (cb->logLevel>=XMLBLASTER_LOG_INFO) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_INFO, __FILE__,
669       "[CallbackServerUnparsed] XmlBlaster connected from %s:%hd",
670                            inet_ntoa(cli_addr.sin_addr), cli_addr.sin_port);
671    return true;
672 }
673 
674 static bool isListening(CallbackServerUnparsed *cb)
675 {
676    if (cb->listenSocket > -1) {
677       return true;
678    }
679    return false;
680 }
681 
682 /**
683  * Parse the update message from xmlBlaster.
684  * <p>
685  * This method blocks until data arrives.
686  * </p>
687  * The socketDataHolder holds all informations about the returned data from xmlBlaster,
688  * on error the exception struct is filled.
689  *
690  * @param cb The 'this' pointer
691  * @param socketDataHolder You need to free(socketDataHolder->data) if return is 'true'.
692  * @param exception Contains the exception thrown (on error only)
693  * @param udp If UDP to use
694  * @return true if OK or on exception, false on EOF
695  */
696 static bool readMessage(CallbackServerUnparsed *cb, SocketDataHolder *socketDataHolder, XmlBlasterException *exception, bool udp)
697 {
698    return parseSocketData(udp ? cb->socketUdp : cb->acceptSocket, &cb->readFromSocket, socketDataHolder,
699                           exception, &cb->stopListenLoop, udp, cb->logLevel >= XMLBLASTER_LOG_DUMP);
700 }
701 
702 /** A helper to cast to SocketDataHolder */
703 static void voidSendResponse(CallbackServerUnparsed *cb, void *socketDataHolder, MsgUnitArr *msgUnitArrP)
704 {
705    sendResponse(cb, (SocketDataHolder *)socketDataHolder, msgUnitArrP);
706 }
707 
708 static void sendResponse(CallbackServerUnparsed *cb, SocketDataHolder *socketDataHolder, MsgUnitArr *msgUnitArrP)
709 {
710    char *rawMsg;
711    size_t rawMsgLen;
712    size_t dataLen = 0;
713    char *data = 0;
714    size_t i;
715    MsgUnit msgUnit; /* we (mis)use MsgUnit for simple transformation of the exception into a raw blob */
716    bool allocated = false;
717    memset(&msgUnit, 0, sizeof(MsgUnit));
718 
719    for (i=0; i<msgUnitArrP->len; i++) {
720       if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
721          "Returning the UpdateReturnQos '%s' to the server.",
722             msgUnitArrP->msgUnitArr[i].responseQos);
723 
724       if (msgUnitArrP->msgUnitArr[i].responseQos != 0) {
725          msgUnit.qos = msgUnitArrP->msgUnitArr[i].responseQos;
726       }
727       else {
728          msgUnit.qos = strcpyAlloc("<qos/>");
729          allocated = true;
730       }
731 
732       if (data == 0) {
733          BlobHolder blob = encodeMsgUnit(&msgUnit, cb->logLevel >= XMLBLASTER_LOG_DUMP);
734          data = blob.data;
735          dataLen = blob.dataLen;
736       }
737       else {
738          BlobHolder blob = encodeMsgUnit(&msgUnit, cb->logLevel >= XMLBLASTER_LOG_DUMP);
739          data = (char *)realloc(data, dataLen+blob.dataLen);
740          memcpy(data+dataLen, blob.data, blob.dataLen);
741          dataLen += blob.dataLen;
742          free(blob.data);
743       }
744    }
745 
746    rawMsg = encodeSocketMessage(MSG_TYPE_RESPONSE, socketDataHolder->requestId,
747                              socketDataHolder->methodName, socketDataHolder->secretSessionId,
748                              data, dataLen, cb->logLevel >= XMLBLASTER_LOG_DUMP, &rawMsgLen);
749    free(data);
750 
751    /*ssize_t numSent =*/(void) cb->writeToSocket.writeToSocketFuncP(cb->updateCbUserData, cb->acceptSocket, rawMsg, (int)rawMsgLen);
752 
753    free(rawMsg);
754 
755    if (allocated) free((char *)msgUnit.qos);
756 }
757 
758 static void voidSendXmlBlasterException(CallbackServerUnparsed *cb, void *socketDataHolder, XmlBlasterException *exception)
759 {
760    sendXmlBlasterException(cb, (SocketDataHolder *)socketDataHolder, exception);
761 }
762 
763 static void sendXmlBlasterException(CallbackServerUnparsed *cb, SocketDataHolder *socketDataHolder, XmlBlasterException *exception)
764 {
765    size_t currpos = 0;
766    char *rawMsg;
767    size_t rawMsgLen;
768    BlobHolder blob;
769    MsgUnit msgUnit; /* we (mis)use MsgUnit for simple transformation of the exception into a raw blob */
770    memset(&msgUnit, 0, sizeof(MsgUnit));
771 
772    msgUnit.qos = exception->errorCode;
773 
774    /* see XmlBlasterException.toByteArr() and parseByteArr() */
775    msgUnit.contentLen = strlen(exception->errorCode) + strlen(exception->message) + 11;
776    msgUnit.content = (char *)calloc(msgUnit.contentLen, sizeof(char));
777 
778    memcpy((char *)msgUnit.content, exception->errorCode, strlen(exception->errorCode));
779    currpos = strlen(exception->errorCode) + 4;
780 
781    memcpy((char *)msgUnit.content+currpos, exception->message, strlen(exception->message));
782 
783    blob = encodeMsgUnit(&msgUnit, cb->logLevel >= XMLBLASTER_LOG_DUMP);
784 
785    rawMsg = encodeSocketMessage(MSG_TYPE_EXCEPTION, socketDataHolder->requestId,
786                              socketDataHolder->methodName, socketDataHolder->secretSessionId,
787                              blob.data, blob.dataLen, cb->logLevel >= XMLBLASTER_LOG_DUMP, &rawMsgLen);
788    free(blob.data);
789    free((char *)msgUnit.content);
790 
791    /*ssize_t numSent =*/(void) cb->writeToSocket.writeToSocketFuncP(cb->updateCbUserData, cb->acceptSocket, rawMsg, (int)rawMsgLen);
792 
793    free(rawMsg);
794 }
795 
796 /**
797  * A helper to cast to SocketDataHolder
798  * Frees msgUnitArrP
799  */
800 static void voidSendResponseOrException(XMLBLASTER_C_bool success, CallbackServerUnparsed *cb, void *socketDataHolder, MsgUnitArr *msgUnitArrP, XmlBlasterException *exception)
801 {
802    sendResponseOrException(success, cb, (SocketDataHolder *)socketDataHolder, msgUnitArrP, exception);
803 }
804 
805 /**
806  * Takes care of both successful responses as well as exceptions
807  * Frees msgUnitArrP
808  */
809 static void sendResponseOrException(XMLBLASTER_C_bool success, CallbackServerUnparsed *cb, SocketDataHolder *socketDataHolder, MsgUnitArr *msgUnitArrP, XmlBlasterException *exception)
810 {
811    if (! (strcmp(socketDataHolder->methodName, XMLBLASTER_UPDATE_ONEWAY) == 0)) {
812       if (success == true) {
813          if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
814             "update(): Sending response for requestId '%s'", socketDataHolder->requestId);
815          sendResponse(cb, socketDataHolder, msgUnitArrP);
816       }
817       else {
818          if (*(exception->errorCode) == 0) {
819             if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
820                "update(): We don't return anything for requestId '%s', the return message will come later by the client update dispatcher thread", socketDataHolder->requestId);
821          }
822          else {
823             if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
824                "update(): Throwing the XmlBlasterException '%s' back to the server:\n%s",
825                    exception->errorCode, exception->message);
826             sendXmlBlasterException(cb, socketDataHolder, exception);
827          }
828       }
829    }
830 
831    freeMsgUnitArr(msgUnitArrP);
832 }
833 
834 /**
835  * Force closing socket
836  */
837 static void closeAcceptSocket(CallbackServerUnparsed *cb)
838 {
839    /* We close even if cb->reusingConnectionSocket is set
840      to react instantly on EOF from server side.
841      Otherwise the client thread would block until socket response timeout happens (one minute)
842    */
843    if (cb->acceptSocket != -1) {
844       closeSocket(cb->acceptSocket);
845       cb->acceptSocket = -1;
846       if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
847          "Closed accept socket");
848    }
849 }
850 
851 /**
852  * Used internally only to close the socket, calling multiple times makes no harm.
853  * <p />
854  * Closes socket, tells listenLoop to terminate but does not wait on thread termination
855  * and does not destroy cb.
856  * <p />
857  *
858  */
859 static void shutdownCallbackServer(CallbackServerUnparsed *cb)
860 {
861    if (cb == 0) return;
862 
863    if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
864          "Shutdown callback server stopListenLoop=%s (changes now to true), reusingConnectionSocket=%s", (cb->stopListenLoop?"true":"false"), (cb->reusingConnectionSocket?"true":"false"));
865 
866    cb->stopListenLoop = true;
867 
868    if (cb->hostCB != 0) {
869       free(cb->hostCB);
870       cb->hostCB = 0;
871    }
872 
873    if (cb->reusingConnectionSocket) {
874       return; /* not our duty, we only have borrowed the socket from the client side connection */
875    }
876 
877 
878    closeAcceptSocket(cb);
879 
880    if (isListening(cb)) {
881       closeSocket(cb->listenSocket);
882       cb->listenSocket = -1;
883       if (cb->logLevel>=XMLBLASTER_LOG_TRACE) cb->log(cb->logUserP, cb->logLevel, XMLBLASTER_LOG_TRACE, __FILE__,
884          "Closed listener socket");
885    }
886 
887    cb->readFromSocket.numReadFuncP = 0;
888 }
889 
890 const char *callbackServerRawUsage(void)
891 {
892    return
893       "\n   -dispatch/callback/plugin/socket/hostname [localhost]"
894       "\n                       The IP where to establish the callback server."
895       "\n                       Can be useful on multi homed hosts."
896       "\n   -dispatch/callback/plugin/socket/port [7611]"
897       "\n                       The port of the callback server.";
898 }
899 
900 #ifdef USE_MAIN_CB
901 /**
902  * Here we receive the callback messages from xmlBlaster
903  */
904 bool myUpdate(MsgUnitArr *msgUnitArr, void *userData, XmlBlasterException *xmlBlasterException, SocketDataHandler socketDataHandler)
905 {
906    size_t i;
907    bool testException = false;
908    for (i=0; i<msgUnitArr->len; i++) {
909       char *xml = messageUnitToXml(&msgUnitArr->msgUnitArr[i]);
910       printf("client.update(): Asynchronous message update arrived:%s\n", xml);
911       free(xml);
912       msgUnitArr->msgUnitArr[i].responseQos = strcpyAlloc("<qos></qos>"); /* Return QoS: Everything is OK */
913    }
914    if (testException) {
915       strncpy0(xmlBlasterException->errorCode, "user.notWanted", XMLBLASTEREXCEPTION_ERRORCODE_LEN);
916       strncpy0(xmlBlasterException->message, "I don't want these messages", XMLBLASTEREXCEPTION_MESSAGE_LEN);
917       return false;
918    }
919    return true;
920 }
921 
922 /**
923  * Invoke: CallbackServerUnparsed -logLevel TRACE
924  */
925 int main(int argc, char** argv)
926 {
927    int iarg;
928    CallbackServerUnparsed *cb;
929 
930    for (iarg=0; iarg < argc; iarg++) {
931       if (strcmp(argv[iarg], "-help") == 0 || strcmp(argv[iarg], "--help") == 0) {
932          const char *pp =
933          "\n  -logLevel            ERROR | WARN | INFO | TRACE [WARN]"
934          "\n\nExample:"
935          "\n  CallbackServerUnparsed -logLevel TRACE -dispatch/callback/plugin/socket/hostname server.mars.universe";
936          printf("Usage:\n%s%s\n", callbackServerRawUsage(), pp);
937          exit(1);
938       }
939    }
940 
941    cb = getCallbackServerUnparsed(argc, argv, myUpdate, 0);
942    printf("[main] Created CallbackServerUnparsed instance, creating listener on socket://%s:%d...\n", cb->hostCB, cb->portCB);
943    cb->runCallbackServer(cb); /* blocks on socket listener */
944 
945    /* This code is reached only on socket EOF */
946 
947    printf("[main] Socket listener is shutdown\n");
948    freeCallbackServerUnparsed(&cb);
949    return 0;
950 }
951 #endif /* USE_MAIN_CB */


syntax highlighted by Code2HTML, v. 0.9.1