/* ***************************************************************** *
 * Copyright 1998 International Business Machines Corporation. All   *
 * Rights Reserved.                                                  *
 *                                                                   *
 * Please read this carefully.  Your use of this reference           *
 * implementation of certain of the IETF public-key infrastructure   *
 * specifications ("Software") indicates your acceptance of the      *
 * following.  If you do not agree to the following, do not install  *
 * or use any of the Software.                                       *
 *                                                                   *
 * Permission to use, reproduce, distribute and create derivative    *
 * works from the Software ("Software Derivative Works"), and to     *
 * distribute such Software Derivative Works is hereby granted to    *
 * you by International Business Machines Corporation ("IBM").  This *
 * permission includes a license under the patents of IBM that are   *
 * necessarily infringed by your use of the Software as provided by  *
 * IBM.                                                              *
 *                                                                   *
 * IBM licenses the Software to you on an "AS IS" basis, without     *
 * warranty of any kind.  IBM HEREBY EXPRESSLY DISCLAIMS ALL         *
 * WARRANTIES OR CONDITIONS, EITHER EXPRESS OR IMPLIED, INCLUDING,   *
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OR CONDITIONS OF       *
 * MERCHANTABILITY, NON INFRINGEMENT AND FITNESS FOR A PARTICULAR    *
 * PURPOSE.  You are solely responsible for determining the          *
 * appropriateness of using this Software and assume all risks       *
 * associated with the use of this Software, including but not       *
 * limited to the risks of program errors, damage to or loss of      *
 * data, programs or equipment, and unavailability or interruption   *
 * of operations.                                                    *
 *                                                                   *
 * IBM WILL NOT BE LIABLE FOR ANY DIRECT DAMAGES OR FOR ANY SPECIAL, *
 * INCIDENTAL, OR  INDIRECT DAMAGES OR FOR ANY ECONOMIC              *
 * CONSEQUENTIAL DAMAGES (INCLUDING LOST PROFITS OR SAVINGS), EVEN   *
 * IF IBM HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.  IBM  *
 * will not be liable for the loss of, or damage to, your records or *
 * data, or any damages claimed by you based on a third party claim. *
 *                                                                   *
 * IBM wishes to obtain your feedback to assist in improving the     *
 * Software.  You grant IBM a world-wide, royalty-free right to use, *
 * copy, distribute, sublicense and prepare derivative works based   *
 * upon any feedback, including materials, error corrections,        *
 * Software Derivatives, enhancements, suggestions and the like that *
 * you provide to IBM relating to the Software (this does not        *
 * include products for which you charge a royalty and distribute to *
 * IBM under other terms and conditions).                            *
 *                                                                   *
 * You agree to distribute the Software and any Software Derivatives *
 * under a license agreement that: 1) is sufficient to notify all    *
 * licensees of the Software and Software Derivatives that IBM       *
 * assumes no liability for any claim that may arise regarding the   *
 * Software or Software Derivatives, and 2) that disclaims all       *
 * warranties, both express and implied, from IBM regarding the      *
 * Software and Software Derivatives.  (If you include this          *
 * Agreement with any distribution of the Software or Software       *
 * Derivatives you will have met this requirement.)  You agree that  *
 * you will not delete any copyright notices in the Software.        *
 *                                                                   *
 * This Agreement is the exclusive statement of your rights in the   *
 * Software as provided by IBM.   Except for the rights granted to   *
 * you in the second paragraph above, You are not granted any other  *
 * patent rights, including but not limited to the right to make     *
 * combinations of the Software with products that infringe IBM      *
 * patents. You agree to comply with all applicable laws and         *
 * regulations, including all export and import laws and regulation. *
 * This Agreement is governed by the laws of the State of New York.  *
 * This Agreement supersedes all other communications,               *
 * understandings or agreements we may have had prior to this        *
 * Agreement.                                                        *
 * ***************************************************************** */

#include "JonahIni.h"
#include "ObjectCache.h"
#include "pkiTask.h"
#include "asnbase.h"

CachedObj::CachedObj(void) {
  prev = NULL;
  next = NULL;
  key = 0;
  deleting = false;
  locked = false;
  modified = false;
  objClass = 0;
  mutex = PKI_MUTEX_INIT;
  pkiCreateMutex(&mutex, 0);
}

CachedObj::~CachedObj() {
  pkiDestroyMutex(&mutex);
}

CachedObj * JnhObjectCache::lookupObject(uint32 key) {
  CachedObj * ptr = root;
// The list-lock is assumed to be held
  while ((ptr != NULL) && (ptr->key != key) && (!ptr->deleting)) ptr = ptr->next;
  return ptr;
}

uint32 JnhObjectCache::loadObject(uint32 key) {
  uint32 st;
//  uint32 objClass;
  CachedObj * ptr;
  buffer_t data;

// Internal function to load an object from disk into a new cache slot.
  ptr = root;
  root = new CachedObj;
  if (ptr) ptr->prev = root;
  root->next = ptr;
  root->prev = NULL;
  root->key = key;
  root->locked = false;
  root->modified = false;
  st = os->FetchObject(key, root->objClass, data);
  if (st) {
// Whoops - Object not on disk.  Delete the cache slot we just created.
    ptr = root;
    root = root->next;
    if (root) root->prev = NULL;
    delete ptr;
    return st;
  };
  st = root->obj.read(data);
  return st;
}

uint32 JnhObjectCache::reloadObject(CachedObj * ptr) {
  uint32 st;
//  uint32 objClass;
  buffer_t data;

  st = os->FetchObject(ptr->key, ptr->objClass, data);
  if (st) return st;
  ptr->locked = false;
  ptr->modified = false;
  st = ptr->obj.read(data);
  return st;
}

uint32 JnhObjectCache::writeObject(CachedObj * ptr) {
  uint32 st;
//  uint32 objClass;
  buffer_t data;

  if (st = ptr->obj.write(data)) return st;
  st = os->StoreObject(ptr->key, 
                       ptr->objClass,
                       data);
  if (st) return st;
  ptr->modified = false;
  return st;

}


JnhObjectCache::JnhObjectCache(int size) {
  char OSName[512];
  char OSPath[512];
  int fsize = size;

// Create and initialize the objectCache. The <size>
// parameter gives a hint as to the number of active 
// objects that the server will want to maintain.  The default 
// value, 10, is probably typical of end-systems; CAs and RAs
// will probably want to deal with more objects.  This parameter 
// is only a hint, and is expected to be used by the objectCache
// in determining when to reclaim memory occupied by unlocked 
// objects.

  root = NULL;
  if (fsize < 512) fsize = 512;
  mutex = PKI_MUTEX_INIT;
  pkiCreateMutex(&mutex, 0);

  if (IniAmICA()) 
    IniReadString("ObjectStore", "Name", OSName, sizeof(OSName), "JonahCA");
  else if (IniAmIRA())
    IniReadString("ObjectStore", "Name", OSName, sizeof(OSName), "JonahRA");
  else IniReadString("ObjectStore", "Name", OSName, sizeof(OSName), "Jonah");

  IniReadString("ObjectStore", "Path", OSPath, sizeof(OSPath), "");
  os = new JnhObjectStore(OSPath, OSName, fsize, 2048);

}

uint32 JnhObjectCache::storeObject(ObjStoreData * objectPtr, 
                                   uint32 objClass, 
                                   uint32 * key) {
  buffer_t buffer;
  uint32 st;

// storeObject saves the provided object in the backing-store,
// and returns the new key.  This is how new objects are placed
// in the objectstore.  The objectstore does not take ownership 
// of the object designated by objectPtr;  A subsequent getObject
// operation using this key will result in a new in-memory object 
// being created.  Therefore the caller should discard the 
// original object after making this call.  If the objectCache
// keeps its own in-memory copy of the object, that copy shall 
// be marked unlocked and unmodified.

  buffer.clear();
  if (st = objectPtr->write(buffer)) return st;
  return os->CreateObject(key, objClass, buffer);

}

uint32 JnhObjectCache::createObject(uint32 objClass, 
                                    ObjStoreData ** objectPtr, 
                                    uint32 * key) {
// createObject creates a new object, assigns a objectstore key to it,
// and returns the new key and object, which is marked locked and modified.  
// The objectStore owns the object designated by objectPtr, so the caller
// must not destroy it.  
  uint32 st;
  buffer_t data;
  CachedObj * ptr;

  pkiLockMutex(&mutex);  
  ptr = root;
  root = new CachedObj;
  if (ptr) ptr->prev = root;
  root->next = ptr;
  root->prev = NULL;
  root->locked = true;
  root->modified = true;
  root->objClass = objClass;
  data.clear();
  st = os->CreateObject(key, objClass, data);
  if (st) {
// Error creating the object in the objectstore.  Back out the 
// new cache slot we created...
    ptr = root;
    root = root->next;
    if (root) root->prev = NULL;
    delete ptr;

    *objectPtr = NULL;
    *key = 0;
    pkiUnlockMutex(&mutex);  
    return ISM_RECORD_NOT_FOUND;  
// Not exactly the error you'd expect, but it'll do.
  }
  pkiLockMutex(&root->mutex);
  root->key = *key;
  *objectPtr = &root->obj;
  *key = root->key;
  pkiUnlockMutex(&mutex);  
  return st;
}

uint32 JnhObjectCache::getObject(uint32 key, 
                                 uint32 * objClass, 
                                 const ObjStoreData ** objectPtr) {
// getObject retrieves a read-only pointer to the object whose 
// key is <key>.  The object must be in memory and locked.

  CachedObj * cachePtr;

  pkiLockMutex(&mutex);
  
  cachePtr = lookupObject(key);


  if (cachePtr == NULL) {
    pkiUnlockMutex(&mutex);
    return ISM_RECORD_NOT_LOCKED;
  };
  if (!cachePtr->locked) {
    pkiUnlockMutex(&mutex);
    return ISM_RECORD_NOT_LOCKED;
  };

  *objClass = cachePtr->objClass;
  *objectPtr = &(cachePtr->obj);

  pkiUnlockMutex(&mutex);
  return 0;
}

uint32 JnhObjectCache::getTransientData(uint32 key, 
                                        TransientData ** transientData) {
// getTransientData retrieves a pointer to the transient data associated
// with the object whose key is <key>.  The object must be in memory and locked.

  CachedObj * cachePtr;

  pkiLockMutex(&mutex);
  
  cachePtr = lookupObject(key);


  if (cachePtr == NULL) {
    pkiUnlockMutex(&mutex);
    return ISM_RECORD_NOT_LOCKED;
  };
  if (!cachePtr->locked) {
    pkiUnlockMutex(&mutex);
    return ISM_RECORD_NOT_LOCKED;
  };

  *transientData = &(cachePtr->transientData);

  pkiUnlockMutex(&mutex);
  return 0;
}




uint32 JnhObjectCache::getObjectModify(uint32 key, 
                                       uint32 * objClass, 
                                       ObjStoreData ** objectPtr) {
// getObjectModify retrieves a pointer to the object whose key 
// is <key>.
  CachedObj * cachePtr;

  pkiLockMutex(&mutex);
  
  cachePtr = lookupObject(key);  // Leave the list locked...

  if (cachePtr == NULL) {
    pkiUnlockMutex(&mutex);
    return ISM_RECORD_NOT_LOCKED;
  };
  if (!cachePtr->locked) {
    pkiUnlockMutex(&mutex);
    return ISM_RECORD_NOT_LOCKED;
  };

  cachePtr->modified = true;
  *objClass = cachePtr->objClass;
  *objectPtr = &(cachePtr->obj);
  pkiUnlockMutex(&mutex);
  return 0;

  
}

uint32 JnhObjectCache::lockObject(uint32 key) {

  CachedObj * ptr = NULL;
  bool try_again = true;
  uint32 status;

  while (try_again) {
    
    try_again = false;
    pkiLockMutex(&mutex);

    ptr = lookupObject(key);
    
    if (ptr != NULL) {
      pkiLockMutex(&ptr->mutex);
      if (ptr->deleting) {
// Whoops - this object is going away.  Let it go and try again...
        pkiUnlockMutex(&ptr->mutex);
        pkiUnlockMutex(&mutex);
        pkiSleep(0);
        try_again = true;
      };
    };
  };

// Now we have the list locked, and either the object is present and locked, or
// we need to load it from disk.

  
  if (ptr == NULL) {
    if (status = JnhObjectCache::loadObject(key)) {
      pkiUnlockMutex(&mutex);
      return status;
    };
    ptr = lookupObject(key);
    if (ptr == NULL) {
      pkiUnlockMutex(&mutex);
      return ISM_RECORD_NOT_FOUND;
    };
    pkiLockMutex(&ptr->mutex);
  };

// Now the record is present and locked.  Mark it so...
  ptr->locked = true;
  pkiUnlockMutex(&mutex);
  return 0;
}


uint32 JnhObjectCache::unlockObject(uint32 key) {
  uint32 st = 0;
  CachedObj * cachePtr;
  
  pkiLockMutex(&mutex);  
  cachePtr = lookupObject(key);
  if (cachePtr == NULL) {
    pkiUnlockMutex(&mutex);  
    return ISM_RECORD_NOT_FOUND;
  };
  if (cachePtr->modified) {
// The object was modified, so discard the changes
    st = reloadObject(cachePtr);
  };
  cachePtr->locked = false;
  pkiUnlockMutex(&cachePtr->mutex);
  pkiUnlockMutex(&mutex);
  return st;
}


uint32 JnhObjectCache::discardObject(uint32 key) {
  return discardObject(key, false);
}

uint32 JnhObjectCache::discardObjectNocheck(uint32 key, bool lockList) {
  uint32 st = 0;
  CachedObj * cachePtr;
  
  pkiLockMutex(&mutex);  
  cachePtr = lookupObject(key);
  if (cachePtr == NULL) {
    if (!lockList) pkiUnlockMutex(&mutex);
    return ISM_RECORD_NOT_FOUND;
  };
  
// remove the cached object from the list...
  if (cachePtr->prev) cachePtr->prev->next = cachePtr->next;
  else root = cachePtr->next;
  if (cachePtr->next) cachePtr->next->prev = cachePtr->prev;

// We need to toggle the lock on the object so if anyone's
// waiting for it, they'll go away...

  cachePtr->deleting = true;
  pkiUnlockMutex(&cachePtr->mutex);
  pkiSleep(0);
  pkiLockMutex(&cachePtr->mutex);
  pkiDestroyMutex(&cachePtr->mutex);
  
  delete cachePtr;

  if (!lockList) pkiUnlockMutex(&mutex);

  return st;
}

uint32 JnhObjectCache::discardObject(uint32 key, bool lockList) {
  uint32 st = 0;
  CachedObj * cachePtr;
  
  pkiLockMutex(&mutex);  
  cachePtr = lookupObject(key);
  if (cachePtr == NULL) {
    if (!lockList) pkiUnlockMutex(&mutex);
    return ISM_RECORD_NOT_FOUND;
  };
  if (!cachePtr->locked) {
    if (!lockList) pkiUnlockMutex(&mutex);
    return ISM_RECORD_NOT_LOCKED;
  };
  
// remove the cached object from the list...
  if (cachePtr->prev) cachePtr->prev->next = cachePtr->next;
  else root = cachePtr->next;
  if (cachePtr->next) cachePtr->next->prev = cachePtr->prev;

// We need to toggle the lock on the object so if anyone's
// waiting for it, they'll go away...

  cachePtr->deleting = true;
  pkiUnlockMutex(&cachePtr->mutex);
  pkiSleep(0);
  pkiLockMutex(&cachePtr->mutex);
  pkiDestroyMutex(&cachePtr->mutex);
  
  delete cachePtr;

  if (!lockList) pkiUnlockMutex(&mutex);

  return st;
}

  
uint32 JnhObjectCache::synchObject(uint32 key) {
// synchObject replaces the designated object in the backing-store.
// If the object is not locked, an error should be returned and no 
// action performed.  If the object's <modified> flag is not set, 
// an error should be returned, and no action performed.  On 
// succesful completion, the object's <modified> flag is cleared, 
// and the object is unlocked.
  uint32 st = 0;
  CachedObj * cachePtr;
  
  pkiLockMutex(&mutex);  
  do {
    cachePtr = lookupObject(key);
    if (cachePtr == NULL) {
      st = ISM_RECORD_NOT_FOUND;
      break;
    };
    if (!cachePtr->locked) {
      st = ISM_RECORD_NOT_LOCKED;
      break;
    };
    if (!cachePtr->modified) {
      st = ISM_RECORD_NOT_MODIFIED;
      break;
    };
// We need to write the object back to disk...
    if (st = writeObject(cachePtr)) break;

    cachePtr->modified = false;
    cachePtr->locked = false;
    pkiUnlockMutex(&cachePtr->mutex);

  } while(0);

  pkiUnlockMutex(&mutex);
  return st;
}
  

uint32 JnhObjectCache::deleteObject(uint32 key) {
// deleteObject deletes the designated object, both from memory and
// from the backing-store.  The object must be locked
// prior to this call, and the state of its <modified> flag is
// not significant.
  uint32 st = 0;
  
  st = discardObject(key, true);
  if (st != 0) {
// Any non-zero status is cause for concern here, since deleteObject
// requires that the user already hold a lock on the object...
    pkiUnlockMutex(&mutex);
    return st;
  };

// Delete the on-disk object...
  st = os->DeleteObject(key);
  pkiUnlockMutex(&mutex);
  return st;
}

JnhObjectCache::~JnhObjectCache() {
// The ObjectCache destructor should destroy all in-memory objects.
  uint32 key;

  pkiLockMutex(&mutex);  
  while (root != NULL) {
    key = root->key;
    pkiUnlockMutex(&mutex);  
    discardObjectNocheck(key, false);
    pkiLockMutex(&mutex);  
  };
  pkiUnlockMutex(&mutex);  
}

uint32 JnhObjectCache::ListActiveObjects(void (* callback)(uint32 key, 
                                                           uint32 objClassSt)) {

// Run through the database, calling <callback> for every object whose 
// objClass is active
  return os->ListActiveObjects(callback);
}

uint32 JnhObjectCache::ListSurrogateObjects(void (* callback)(uint32 key, 
                                                              uint32 objClassSt)) {
// Run through the database, calling <callback> for every object whose 
// objClass is surrogate
  return os->ListSurrogateObjects(callback);
}

uint32 JnhObjectCache::ListObjectsByClass(uint32 objClass,
                                          void (* callback)(uint32 key, 
                                                            uint32 objClassSt)) {
// Run through the database, calling <callback> for every object
// whose objClass intersects the specified class (masked with ObjClAll)
  return os->ListObjectsByClass(objClass, callback);
}

uint32 JnhObjectCache::ListObjectsByState(uint32 objState,
                                          void (* callback)(uint32 key, 
                                                            uint32 objClassSt)) {
// Run through the database, calling <callback> for every object
// whose state matches the specified state (masked with ObjStAll)
  return os->ListObjectsByState(objState, callback);
}


uint32 JnhObjectCache::getObjectFlags(uint32 key, uint32 * objClass) {
// getObjectFlags returns the flags associated with the specified
// object.
  const ObjStoreData * objectPtr;
  return getObject(key, objClass, &objectPtr);
}

uint32 JnhObjectCache::setObjectFlags(uint32 key, uint32 objClass) {
// setObjectFlags sets the flags associated with the specified
// object.  The object must be marked open for modify.

//  uint32 st;
  CachedObj * cachePtr;
  
  pkiLockMutex(&mutex);  
  cachePtr = lookupObject(key);

  if (cachePtr == NULL) {
    pkiUnlockMutex(&mutex);
    return ISM_RECORD_NOT_LOCKED;
  };

  if (!cachePtr->locked) {
    pkiUnlockMutex(&mutex);
    return ISM_RECORD_NOT_LOCKED;
  };

  if (!cachePtr->modified) {
    pkiUnlockMutex(&mutex);
    return ISM_RECORD_NOT_MODIFIED;
  };

  cachePtr->objClass = objClass;

  pkiUnlockMutex(&mutex);
  return 0;

}
