/****************************************************************************/
/*                                                                          */
/* Program name: MQIsdp protocol C Language implementation                  */
/*                                                                          */
/* Description: This source file contains functions for handing all Inter   */
/* Process Communication between the various threads.                       */
/*                                                                          */
/*  Statement:  Licensed Materials - Property of IBM                        */
/*                                                                          */
/*              MQSeries SupportPac IA93                                    */
/*              (C) Copyright IBM Corp. 2002                                */
/*                                                                          */
/****************************************************************************/
/* Version @(#) IA93/ship/mspipc.c, SupportPacs, S000 1.3 03/08/26 16:38:24  */
/*                                                                          */
/* Function:                                                                */
/*                                                                          */
/* Functions such as mspInitialiseIPC, mspWriteIPC and mspReadIPC are used  */
/* for transferring data between threads. mspLockMutex and mspReleaseMutex  */
/* control when data may be sent to a particular thread (a lock must be     */
/* obtained before data can be written to a thread). mspWaitForSemaphore    */
/* and mspSignalSemaphore are used when an application does a blocking wait */
/* for messages to arrive.                                                  */
/*                                                                          */
/****************************************************************************/
/*                                                                          */
/* Change history:                                                          */
/*                                                                          */
/* V1.0   19-02-2003  IRH  Initial release                                  */
/* V1.1   18-08-2003  IRH  mspReadIPC - pointer returned from mspRealloc was*/
/*                         not correctly assigned to *ppBuffer variable.    */   
/*                                                                          */
/*==========================================================================*/
/* Module Name: mspipc.c                                                    */
#include <mspsh.h>

/* Initialise the IPC read timeout, if appropriate                              */
/* A timeout value of -1 will cause the mailbox to block until data is received */
DllExport int mspSetIPCTimeout( MBH mbHandle, long mSecs ) {
    #ifndef MSP_SINGLE_THREAD
    
    #if defined(WIN32)
    
      if ( mSecs == -1 ) {
          SetMailslotInfo( mbHandle, MSP_IPC_WAIT_FOREVER );
      } else {
          SetMailslotInfo( mbHandle, mSecs );
      }

    #endif
    
    #endif
    
    return 0;
}

/* Initialise the IPC, as appropriate                                           */
/* A timeout value of -1 will cause the mailbox to block until data is received */
DllExport int mspInitialiseIPC( MBH mbHandle, IPCCB *pIpcCb ) {
    #ifndef MSP_SINGLE_THREAD
    
    #if defined(WIN32)
      SetMailslotInfo( mbHandle, pIpcCb->readTimeout );
    #elif defined(UNIX)
      /* On UNIX we want to ignore the SIGPIPE signal */
      signal( SIGPIPE, SIG_IGN );

    #endif
    
    #endif
    
    return 0;
}

/* mspWriteIPC: Used by the tasks/threads to exchange data.     */
/* Returns 1 on error, otherwise 0                              */
DllExport int mspWriteIPC( MBH mbHandle, IPCCB *pIpcCb, char *ec, int retCode,
                           MQISDPMH hMsg, long numBytes, char *pBuffer ) {
    int     rc = 0;
    CB_HEAD ipcHeader; 
    #ifndef MSP_SINGLE_THREAD
    long    bytesWritten = 0;
    #endif /* MSP_SINGLE_THREAD */
    
    /* Setup the IPC header */
    memcpy( ipcHeader.eyeCatcher, ec, MSP_EC_LENGTH );
    ipcHeader.returnCode = retCode;
    ipcHeader.hMsg = hMsg;
    ipcHeader.dataLength = numBytes;
    ipcHeader.pData = pBuffer;

    #ifndef MSP_SINGLE_THREAD
   
    #if defined(WIN32)
    
       if ( WriteFile( mbHandle, &ipcHeader, sizeof(CB_HEAD), &bytesWritten, NULL ) == 0 ) {
           printf( "IPC: WriteFile error:%ld\n", GetLastError() );
           rc = 1;
       }
    #elif defined(UNIX)
    
       if ( write( mbHandle, &ipcHeader, sizeof(CB_HEAD) ) < 0 ) {
           printf( "IPC: write error:%ld\n", errno );
           rc = 1;
       }

    #endif

    #else
    memcpy( pIpcCb->pPseudoMailbox, &ipcHeader, sizeof(CB_HEAD) );
    #endif

    return rc;
}

/* mspReadIPC: Used by the daemon and client tasks to read data */
/* from the partner task (client or daemon respectively).       */
/* Returns 1 on error, otherwise 0                              */
/* NOTE: nBytesRead may be 0 even if data is read, because this */
/* only counts data received in addition to the CB_HEAD header. */
/* The function return code should be used to determine if data */
/* was received or not.                                         */
DllExport int mspReadIPC( MBH mbHandle, IPCCB *pIpcCb, MSPCMN *comParms, long *nBytesRead,
                          long *bufSize, void **ppBuffer, char *ec ,int *retCode,
                          MQISDPMH *phMsg ) {
    int     rc = 0;
    CB_HEAD ipcHeader;
    long    bytesRead = 0;

    #ifndef MSP_SINGLE_THREAD
    
      #if defined(WIN32)
      if ( ReadFile( mbHandle, &ipcHeader, sizeof(CB_HEAD), &bytesRead, NULL ) == 0 ) {
          if ( GetLastError() != ERROR_SEM_TIMEOUT) {
              printf( "IPC: ReadFile error:%ld\n", GetLastError() );
          }
          bytesRead = 0;
          rc = 1;
      }

      #elif defined(UNIX)
      int selRc;

      /* Wait for any IPC input down the pipe */
      selRc = msp_select( mbHandle, pIpcCb->readTimeout );
      if( selRc > 0 ) {
          /* Bytes available */
          bytesRead = read( mbHandle, &ipcHeader, sizeof(CB_HEAD) );
          if ( bytesRead < 0 ) {
              printf( "IPC: read error:%ld\n", errno );
              bytesRead = 0;
              rc = 1;
          }
      } else {
          /* No data available - msp_select timeout (0) or an error (<0) */
          if ( selRc < 0 ) {
              printf( "IPC: read select error:%ld\n", errno );
          }
          bytesRead = 0;
          rc = 1;
      }

      #endif
      
    #else /* MSP_SINGLE_THREAD */
      memcpy( &ipcHeader, pIpcCb->pPseudoMailbox, sizeof(CB_HEAD) );
      bytesRead = sizeof(CB_HEAD);
    #endif /* MSP_SINGLE_THREAD */

    if ( bytesRead > 0 ) {
        /* Copy the relevant information out of the IPC header */
        *nBytesRead = ipcHeader.dataLength;
        memcpy( ec, ipcHeader.eyeCatcher, MSP_EC_LENGTH );
        *retCode = ipcHeader.returnCode;
        if ( phMsg != NULL ) {
            *phMsg = ipcHeader.hMsg;
        }

        /* Now copy the received data into the receive buffer, extending the buffer if required */
        if ( ipcHeader.dataLength > 0 ) {
            if ( ipcHeader.dataLength <= *bufSize ) {
                memcpy( *ppBuffer, ipcHeader.pData, ipcHeader.dataLength );
            } else {
                *ppBuffer = mspRealloc( comParms, *ppBuffer, ipcHeader.dataLength, *bufSize );
                *bufSize = ipcHeader.dataLength;
                memcpy( *ppBuffer, ipcHeader.pData, ipcHeader.dataLength );
            }
        }
    } else {
        *nBytesRead = 0;
        memset( ec, ' ', MSP_EC_LENGTH );
        *retCode = MQISDP_FAILED;
    }

    return rc;
}

/* Waits for a mutex to become signalled */
/* Returns 0 if this task owns the mutex */
/* Returns 1 otherwise                   */
DllExport int mspLockMutex( MTH mutexHandle ) {
    int rc = 0;
    
    #ifndef MSP_SINGLE_THREAD

    #if defined(WIN32)
      /* Wait forever */
      rc = WaitForSingleObject( mutexHandle, INFINITE );
      if ( rc == WAIT_TIMEOUT ) {
          rc = 1;
      }

    #elif defined(UNIX)
      struct sembuf sb;

      sb.sem_num = 0;
      sb.sem_op = -1;
      /* If the process fails we want the kernel to undo the semaphore */
      sb.sem_flg = SEM_UNDO;

      if ( semop( mutexHandle, &sb, 1 ) < 0 ) {
          rc = 1;
      }

    #endif
    
    #else    /* MSP_SINGLE_THREAD */
      rc = 0;
    #endif   /* MSP_SINGLE_THREAD */
    
    return rc;
}

/* mspReleaseMutex: Release the mutex identified by MTH */
DllExport int mspReleaseMutex( MTH mutexHandle ) {
    int rc = 1;

    #ifndef MSP_SINGLE_THREAD
    
    #if defined(WIN32)
    
      if ( ReleaseMutex( mutexHandle ) > 0 ) { 
          /* Success */
          rc = 0;
      }

    #elif defined(UNIX)
      struct sembuf sb;

      sb.sem_num = 0;
      sb.sem_op = 1;
      sb.sem_flg = SEM_UNDO;

      if ( semop( mutexHandle, &sb, 1 ) < 0 ) {
          rc = 1;
      } else {
          rc = 0;
      }

    #endif
    
    #else   /* MSP_SINGLE_THREAD */
      rc = 0;
    #endif  /* MSP_SINGLE_THREAD */
    
    return rc;
}

/* Waits for a semaphore to become signalled                   */
/* Returns 0 if this task waits successfully for the semaphore */
/* Returns 1 otherwise e.g. timeout                            */
DllExport int mspWaitForSemaphore( MSH semHandle, long msTimeout ) {
    int rc = 1;

    #ifndef MSP_SINGLE_THREAD
    
    #if defined(WIN32)
      if ( msTimeout == -1 ) {
          /* Wait forever */
          rc = WaitForSingleObject( semHandle, INFINITE );
      } else {
          rc = WaitForSingleObject( semHandle, msTimeout );
      }
      if ( rc == WAIT_TIMEOUT ) {
          rc = 1;
      } else {
          rc = 0;
      }
    #elif defined(UNIX)
      struct timespec to;

      /* Lock the pthread mutex                           */
      /* Check if a message is already available          */
      /* If there is no message then wait to be signalled */
      /*    either wait forever, or on a timer            */
      /* The message flag is checked first, as a message may have arrived */
      /* whilst we were not listening for condition variable events.      */
      pthread_mutex_lock( &semHandle->semLock );
      if ( semHandle->msgAvailable == 0x00 ) {
          /* No message available, so wait */
          if ( msTimeout == -1 ) {
              /* Wait forever */
              rc = pthread_cond_wait( &semHandle->msgSignal, &semHandle->semLock );
          } else {

              to.tv_sec = time(NULL) + (int)(msTimeout / 1000);
              to.tv_nsec = 0;
              /* to.tv_nsec = (int)(msTimeout % 1000); */
              rc = pthread_cond_timedwait( &semHandle->msgSignal, &semHandle->semLock, &to );
          }

          if ( rc == ETIMEDOUT ) {
              rc = 1;
          } else {
              rc = 0;
          }
      } else {
          /* A message is available */
          rc = 0;
      }
      /* Reset the message available flag */
      semHandle->msgAvailable = 0x00;
      pthread_mutex_unlock( &semHandle->semLock );

    #endif
    
    #else   /* MSP_SINGLE_THREAD */
      rc = 0;
    #endif  /* MSP_SINGLE_THREAD */
    
    return rc;
}

/* mspSignalSemaphore: Release the semaphore to indicate that data is available */
/* to be received.                                                              */
DllExport int mspSignalSemaphore( MSH semHandle ) {
    int rc = 1;

    #ifndef MSP_SINGLE_THREAD
    
    #if defined(WIN32)
      if ( ReleaseSemaphore( semHandle, 1, NULL ) > 0 ) { 
          /* Success */
          rc = 0;
      }

    #elif defined(UNIX)
      
      /* Lock the pthread mutex                           */
      /* Set the message available flag                   */
      /* Signal any waiters                               */
      /* A message flag is used as well as signalling because a signal is only */
      /* caught if there is a thread waiting. The msgAvailable flag holds the  */
      /* correct state regardless of whether there is a thread waiting.        */
      pthread_mutex_lock( &semHandle->semLock );
      
      /* Set the message available flag and signal any waiters */
      semHandle->msgAvailable = 0x01;
      pthread_cond_signal( &semHandle->msgSignal );
      
      pthread_mutex_unlock( &semHandle->semLock );
      rc = 0;

    #endif
    
    #else   /* MSP_SINGLE_THREAD */
      rc = 0;
    #endif  /* MSP_SINGLE_THREAD */
    
    return rc;
}
            

