modules/snmpstats/interprocess_buffer.c
2709a6b8
 /*
  * SNMPStats Module 
  * Copyright (C) 2006 SOMA Networks, INC.
  * Written by: Jeffrey Magder (jmagder@somanetworks.com)
  *
27642a08
  * This file is part of Kamailio, a free SIP server.
2709a6b8
  *
27642a08
  * Kamailio is free software; you can redistribute it and/or modify it
2709a6b8
  * under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
  * (at your option) any later version
  *
27642a08
  * Kamailio is distributed in the hope that it will be useful, but
2709a6b8
  * WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
9e1ff448
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
2709a6b8
  * USA
  *
94dd7c5f
  */
 
 /*!
  * \file
  * \brief SNMP statistic module, interprocess buffer
9f97c5e5
  * \author jmagder
2709a6b8
  *
  * This file implements the interprocess buffer, used for marshalling data
68d7468c
  * exchange from the usrloc module to the kamailioSIPRegUserTable,
  * kamailioSIPContactTable, and indirectly the kamailioSIPRegUserLookupTable.
94dd7c5f
  *
2709a6b8
  * Details on why the interprocess buffer is needed can be found in the comments
  * at the top of interprocess_buffer.h
94dd7c5f
  * \ingroup snmpstats
ca00a464
  * - Module: \ref snmpstats
2709a6b8
  */
 
 
 #include <net-snmp/net-snmp-config.h>
 #include <net-snmp/net-snmp-includes.h>
 #include <net-snmp/agent/net-snmp-agent-includes.h>
 
 #include "interprocess_buffer.h"
2a25dc59
 #include "snmpSIPContactTable.h"
 #include "snmpSIPRegUserTable.h"
2709a6b8
 #include "hashTable.h"
 #include "utilities.h"
 
 #include "../usrloc/ul_callback.h"
 
94dd7c5f
 /*!
2709a6b8
  * The hash table:
  *
  *    1) maps all aor's to snmp's UserIndex for help in deleting SNMP Rows.
  *
  *    2) maps a given aor to a contact list. 
  */
06e71ad9
 hashSlot_t *hashTable = NULL;
2709a6b8
 
94dd7c5f
 /*! All interprocess communication is stored between these two declarations. */
06e71ad9
 interprocessBuffer_t *frontRegUserTableBuffer = NULL;
 interprocessBuffer_t *endRegUserTableBuffer = NULL;
2709a6b8
 
94dd7c5f
 /*! This is to protect the potential racecondition in which a command is added to
2709a6b8
  * the buffer while it is being consumed */
06e71ad9
 gen_lock_t           *interprocessCBLock = NULL;
2709a6b8
 
94dd7c5f
 /*!
2709a6b8
  * This function takes an element of the interprocess buffer passed to it, and
  * handles populating the respective user and contact tables with its contained
  * data.  
  */
 static void executeInterprocessBufferCmd(interprocessBuffer_t *currentBuffer);
 
94dd7c5f
 /*!
2709a6b8
  * Initialize shared memory used to buffer communication between the usrloc
  * module and the SNMPStats module.  (Specifically, the user and contact tables)
  */
07bd572b
 int initInterprocessBuffers(void) 
2709a6b8
 {
 	/* Initialize the shared memory that will be used to buffer messages
 	 * over the usrloc module to RegUserTable callback. */
 	frontRegUserTableBuffer =  shm_malloc(sizeof(interprocessBuffer_t));
 	endRegUserTableBuffer   =  shm_malloc(sizeof(interprocessBuffer_t));
94dd7c5f
 
98a9961a
     if(frontRegUserTableBuffer == NULL || endRegUserTableBuffer == NULL)
     {
         LM_ERR("no more shared memory\n");
         return -1;
     }
2709a6b8
 
 	memset(frontRegUserTableBuffer, 0x00, sizeof(interprocessBuffer_t));
 	memset(endRegUserTableBuffer,   0x00, sizeof(interprocessBuffer_t));
 
 	/* Initialize a lock to the interprocess buffer.  The lock will be used
 	 * to control race-conditions that would otherwise occur if an snmp
 	 * command was received while the interprocess buffer was being consumed.
 	 */
 	interprocessCBLock = lock_alloc();
06e71ad9
 	if(interprocessCBLock==NULL)
 	{
         LM_ERR("cannot allocate the lock\n");
         shm_free(frontRegUserTableBuffer);
         frontRegUserTableBuffer = NULL;
         shm_free(endRegUserTableBuffer);
         endRegUserTableBuffer = NULL;
         return -1;
 	}
2709a6b8
 	lock_init(interprocessCBLock);
 
 	hashTable = createHashTable(HASH_SIZE);
98a9961a
     if(hashTable == NULL)
     {
         LM_ERR("no more shared memory\n");
06e71ad9
 		lock_destroy(interprocessCBLock);
 		lock_dealloc(interprocessCBLock);
98a9961a
         shm_free(frontRegUserTableBuffer);
         frontRegUserTableBuffer = NULL;
         shm_free(endRegUserTableBuffer);
         endRegUserTableBuffer = NULL;
         return -1;
     }
2709a6b8
 
 	return 1;
 }
 
94dd7c5f
 /*! USRLOC Callback Handler:
2709a6b8
  *
  * This function should be registered to receive callbacks from the usrloc
  * module.  It can be called for any of the callbacks listed in ul_callback.h.
  * The callback type will be passed in 'type', and the contact the callback
  * applies to will be supplied in 'contactInfo.  This information will be copied
  * into the interprocess buffer.  The interprocess buffer will be consumed at a
  * later time, when consumeInterprocessBuffer() is called.  
  *
  * This callback is thread safe with respect to the consumeInterprocessBuffer()
  * function.  Specifically, the interprocess buffer should not be corrupted by
  * any race conditions between this function and the consumeInterprocessBuffer()
  * function.
  */
 void handleContactCallbacks(ucontact_t *contactInfo, int type, void *param) 
 {
 	char *addressOfRecord;
 	char *contact;
 
 	interprocessBuffer_t *currentBufferElement;
 
 	currentBufferElement = shm_malloc(sizeof(interprocessBuffer_t));
 
 	if (currentBufferElement == NULL) 
 	{
 		goto error;
 	}
 
 	/* We need to maintain our own copies of the AOR and contact address to
 	 * prevent the corruption of our internal data structures.  
 	 *
 	 * If we do not maintain our own copies, then the AOR and contact adress
 	 * pointed to could be removed and reallocated to another thread before
 	 * we get a chance to consume our interprocess buffer.  */
 	convertStrToCharString(contactInfo->aor,  &addressOfRecord);
 	convertStrToCharString(&(contactInfo->c), &contact);
 
 	currentBufferElement->stringName    = addressOfRecord;
 	currentBufferElement->stringContact = contact;
 	currentBufferElement->contactInfo   = contactInfo;
 	currentBufferElement->callbackType  = type;
 	currentBufferElement->next          = NULL;
 
 
 	/* A lock is necessary to prevent a race condition.  Specifically, it
 	 * could happen that we find the front of the buffer to be non-null,
 	 * are scheduled out, the entire buffer (or part of it) is consumed and
 	 * freed, and then we assign our list to deallocated memory. */
 	lock_get(interprocessCBLock);
 
 	/* This is the first element to be added. */
 	if (frontRegUserTableBuffer->next == NULL) {
 		frontRegUserTableBuffer->next     = currentBufferElement;
 	} else {
 		endRegUserTableBuffer->next->next = currentBufferElement;
 	}
 	
 	endRegUserTableBuffer->next   = currentBufferElement;
 
 	lock_release(interprocessCBLock);
 	
 	return;
 
 error:
68d7468c
 	LM_ERR("Not enough shared memory for  kamailioSIPRegUserTable insert."
de7fe5e9
 			" (%s)\n", contactInfo->c.s);
2709a6b8
 }
 
 
94dd7c5f
 /*! Interprocess Buffer consumption Function.  This function will iterate over
2709a6b8
  * every element of the interprocess buffer, and add or remove the specified
  * contacts and users.  Whether the contacts are added or removed is dependent
  * on if the original element was added as a result of a UL_CONTACT_INSERT or
  * UL_CONTACT_EXPIRE callback.
  *
  * The function will free any memory occupied by the interprocess buffer.
  *
94dd7c5f
  * \note This function is believed to be thread safe.  Specifically, it protects
2709a6b8
  *       corruption of the interprocess buffer through the interprocessCBLock.
  *       This ensures no corruption of the buffer by race conditions.  The lock
  *       has been designed to be occupied for as short a period as possible, so 
  *       as to prevent long waits.  Specifically, once we start consumption of 
  *       the list, other processes are free to continue even before we are done.
  *       This is made possible by simply changing the head of the interprocess
  *       buffer, and then releasing the lock.  
  */
07bd572b
 void consumeInterprocessBuffer(void) 
2709a6b8
 {
 	interprocessBuffer_t *previousBuffer;
 	interprocessBuffer_t *currentBuffer;
 	
 	/* There is nothing to consume, so just exit. */
 	if (frontRegUserTableBuffer->next == NULL) 
 	{
 		return;
 	}
 
 	/* We are going to consume the entire buffer, but we don't want the
 	 * buffer to change midway through.  So assign the front of the buffer
 	 * to NULL so that any other callbacks from the usrloc module will be
 	 * appended to a new list.  We need to be careful to get a lock first
 	 * though, to avoid race conditions. */
 	lock_get(interprocessCBLock);
 
 	currentBuffer = frontRegUserTableBuffer->next;
 	
 	frontRegUserTableBuffer->next = NULL;
 	endRegUserTableBuffer->next   = NULL;
 
 	lock_release(interprocessCBLock);
 
 	while (currentBuffer != NULL) {
 
 		executeInterprocessBufferCmd(currentBuffer);
 
 		/* We need to assign the current buffer to a temporary place
 		 * before we move onto the next buffer.  Otherwise the memory
 		 * could be modified between freeing it and moving onto the next
 		 * buffer element. */
 		previousBuffer = currentBuffer;
 		currentBuffer = currentBuffer->next;
98a9961a
 		shm_free(previousBuffer->stringName);
 		shm_free(previousBuffer->stringContact);
2709a6b8
 		shm_free(previousBuffer);
 
 	}
 
 }
 
 
94dd7c5f
 /*!
2709a6b8
  * This function takes an element of the interprocess buffer passed to it, and
  * handles populating the respective user and contact tables with its contained
  * data.  
  */
 static void executeInterprocessBufferCmd(interprocessBuffer_t *currentBuffer) 
 {
 	int delContactIndex;
 
 	aorToIndexStruct_t *currentUser;
 
 	if (currentBuffer->callbackType == UL_CONTACT_INSERT) 
 	{
 		/* Add the user if the user doesn't exist, or increment its 
 		 * contact index otherwise. */
 		updateUser(currentBuffer->stringName);
 	}
 	else if (currentBuffer->callbackType != UL_CONTACT_EXPIRE)
 	{
 		/* Currently we only support UL_CONTACT_INSERT and
 		 * UL_CONTACT_EXPIRE.  If we receive another callback type, this
 		 * is a bug. */
de7fe5e9
 		LM_ERR("found a command on the interprocess buffer that"
 				" was not an INSERT or EXPIRE");
2709a6b8
 		return;
 	}
 
 	currentUser =
 		findHashRecord(hashTable, currentBuffer->stringName, HASH_SIZE);
 
 
 	/* This should never happen.  This is more of a sanity check. */
 	if (currentUser == NULL) {
de7fe5e9
 		LM_ERR("Received a request for contact: %s for user: %s who doesn't "
 				"exists\n", currentBuffer->stringName, 
2709a6b8
 				currentBuffer->stringContact);
 		return;
 	} 
 
 	/* This buffer element specified that we need to add a contact.  So lets
 	 * add them */
 	if (currentBuffer->callbackType == UL_CONTACT_INSERT) {
 
 		/* Increment the contact index, which will be used to generate
 		 * our new row.  */  
 		currentUser->contactIndex++;
 
 		/* We should do this after we create the row in the snmptable.
 		 * Its easier to delete the SNMP Row than the contact record. */
 		if(!insertContactRecord(&(currentUser->contactList), 
 			currentUser->contactIndex, 
 				currentBuffer->stringContact)) {
 
68d7468c
 			LM_ERR("kamailioSIPRegUserTable was unable to allocate memory for "
de7fe5e9
 					"adding contact: %s to user %s.\n",
 					currentBuffer->stringName, currentBuffer->stringContact);
2709a6b8
 
 			/* We didn't use the index, so decrement it so we can
 			 * use it next time around. */
 			currentUser->contactIndex--;
 			
 			return;
 		}
 	
 		if (!createContactRow(currentUser->userIndex, 
 					currentUser->contactIndex,
 					currentBuffer->stringContact, 
 					currentBuffer->contactInfo)) {
 		
 			deleteContactRecord(&(currentUser->contactList), 
 					currentBuffer->stringContact);
 
 		}
 
 	}
 	else {
 
 		delContactIndex = 
 			deleteContactRecord(&(currentUser->contactList), 
 					currentBuffer->stringContact);
 
 		/* This should never happen.  But its probably wise to check and
 		 * to print out debug messages in case there is a hidden bug.  */
 		if(delContactIndex == 0) {
 			
de7fe5e9
 			LM_ERR("Received a request to delete contact: %s for user: %s"
 				"  who doesn't exist\n", currentBuffer->stringName,
2709a6b8
 				currentBuffer->stringContact);
 			return;
 
 		}		
 
 		deleteContactRow(currentUser->userIndex, delContactIndex);
 
 		deleteUser(hashTable, currentBuffer->stringName, HASH_SIZE);
 	}
 }
 
98a9961a
 void freeInterprocessBuffer(void)
 {
     interprocessBuffer_t *currentBuffer, *previousBuffer;
 
06e71ad9
 	if (frontRegUserTableBuffer==NULL
 			|| frontRegUserTableBuffer->next == NULL
 			|| endRegUserTableBuffer==NULL) {
98a9961a
         LM_DBG("Nothing to clean\n");
 		return;
 	}
 
 	currentBuffer = frontRegUserTableBuffer->next;
 	
 	frontRegUserTableBuffer->next = NULL;
 	endRegUserTableBuffer->next   = NULL;
 
 
 	while (currentBuffer != NULL) {
 
         previousBuffer = currentBuffer;
         currentBuffer = currentBuffer->next;
         shm_free(previousBuffer->stringName);
         shm_free(previousBuffer->stringContact);
         shm_free(previousBuffer);
 
 	}
94dd7c5f
 
98a9961a
     if(frontRegUserTableBuffer)
         shm_free(frontRegUserTableBuffer);
 
     if(endRegUserTableBuffer)
         shm_free(endRegUserTableBuffer);
 
 }