#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <notify.h>
#include <kvbuf.h>
#include <syslog.h>
#include <assert.h>
#include <stack>
#include <DirectoryServiceCore/DSSemaphore.h>
#include <DirectoryServiceCore/CLog.h>
#include "CCache.h"
#include "CPlugInList.h"
extern CPlugInList *gPlugins;
using namespace std;
#pragma mark sCacheValidation structure functions
sCacheValidation::sCacheValidation( const char *inNode )
{
static CFMutableSetRef nodeNameSet = NULL;
static DSSemaphore nodeNameSetLock;
nodeNameSetLock.WaitLock();
if ( nodeNameSet == NULL )
nodeNameSet = CFSetCreateMutable( kCFAllocatorDefault, 0, &kCFTypeSetCallBacks );
CFStringRef nodeName = CFStringCreateWithCString( kCFAllocatorDefault, inNode, kCFStringEncodingUTF8 );
if ( nodeName != NULL )
{
CFStringRef tempName = (CFStringRef) CFSetGetValue( nodeNameSet, nodeName );
if ( tempName != NULL ) {
fNode = (CFStringRef) CFRetain( tempName );
} else {
CFSetSetValue( nodeNameSet, nodeName );
fNode = (CFStringRef) CFRetain( nodeName );
}
DSCFRelease( nodeName );
}
nodeNameSetLock.SignalLock();
fToken = GetToken();
fNodeAvailable = true;
fRefCount = 1;
}
uint32_t sCacheValidation::GetToken( void )
{
char tempNode[512] = { 0, }; char *nodeName;
uint32_t iToken = 0;
CFStringGetCString( fNode, tempNode, sizeof(tempNode), kCFStringEncodingUTF8 );
nodeName = strtok( tempNode, "/" );
if ( nodeName != NULL )
iToken = gPlugins->GetValidDataStamp( nodeName );
return iToken;
}
#pragma mark -
#pragma mark sCacheEntry structure
sCacheEntry::sCacheEntry( int32_t inTTL, time_t inTimeStamp, uint32_t inFlags, kvbuf_t *inBuffer )
{
fValidation = NULL;
fTTL = inTTL;
fBestBefore = inTimeStamp + inTTL;
fLastAccess = inTimeStamp;
fHits = 0;
fRefCount = 1;
fFlags = inFlags;
fBuffer = inBuffer;
}
sCacheEntry::~sCacheEntry( void )
{
DbgLog( kLogDebug, "CCache::CacheEntry:delete entry %X", this );
DSRelease( fValidation );
if ( fBuffer != NULL )
{
kvbuf_free( fBuffer );
fBuffer = NULL;
}
}
bool sCacheEntry::Validate( time_t inNow )
{
bool outValid = false;
if ( this != NULL )
{
if ( fValidation != NULL )
{
if ( fValidation->fNodeAvailable == false ||
(fValidation->IsValid() == true && (fBestBefore == 0 || inNow < fBestBefore)) )
{
outValid = true;
}
}
else if ( fBestBefore == 0 || inNow < fBestBefore )
{
outValid = true;
}
}
return outValid;
}
bool sCacheEntry::MakeValueNonPurgeable( void *value, void *user_data )
{
sCacheEntry *entry = (sCacheEntry *) value;
if ( entry == NULL || entry->fBuffer == NULL )
return true;
#if USE_PURGEABLE_FOR_SMALL_ALLOCATIONS
if ( malloc_make_nonpurgeable(entry) != 0 )
return false;
#endif
if ( kvbuf_make_nonpurgeable(entry->fBuffer) == 0 )
{
DbgLog( kLogDebug, "CCache::CacheEntry::MakeValueNonPurgeable for %X (buffer %X) - SUCCESS", entry, entry->fBuffer );
return true;
}
#if USE_PURGEABLE_FOR_SMALL_ALLOCATIONS
malloc_make_purgeable( entry );
#endif
DbgLog( kLogDebug, "CCache::CacheEntry::MakeValueNonPurgeable for %X (buffer %X) - FAIL", entry, entry->fBuffer );
return false;
}
void sCacheEntry::MakeValuePurgeable( void *value, void *user_data )
{
sCacheEntry *entry = (sCacheEntry *) value;
if ( entry != NULL && entry->fBuffer != NULL ) {
DbgLog( kLogDebug, "CCache::CacheEntry::MakeValuePurgeable for %X (buffer %X)", entry, entry->fBuffer );
kvbuf_make_purgeable( entry->fBuffer );
}
#if USE_PURGEABLE_FOR_SMALL_ALLOCATIONS
malloc_make_purgeable( entry );
#endif
}
#pragma mark -
#pragma mark Cache public routines
CCache::CCache( int32_t inTTL, uint32_t inPolicyFlags ) : fCacheLock("CCache::fCacheLock")
{
fCacheTTL = inTTL;
fPolicyFlags = inPolicyFlags;
fCache = NULL;
cache_attributes_t attrs = {
version : CACHE_ATTRIBUTES_VERSION_1,
key_hash_cb : cache_key_hash_cb_cstring,
key_is_equal_cb : cache_key_is_equal_cb_cstring,
key_retain_cb : CCache::KeyRetain,
key_release_cb : cache_release_cb_free,
value_release_cb : CCache::CacheEntryRelease,
value_make_nonpurgeable_cb : sCacheEntry::MakeValueNonPurgeable,
value_make_purgeable_cb : sCacheEntry::MakeValuePurgeable,
user_data : NULL
};
cache_create( "com.apple.DirectoryService.CCache", &attrs, &fCache );
}
CCache::~CCache( void )
{
Flush();
}
sCacheEntry *CCache::CreateEntry( kvbuf_t *inBuffer, char *inKey, int32_t inTTL, uint32_t inFlags )
{
sCacheEntry *out = NULL;
if ( this != NULL && inKey != NULL )
{
bool bAddEntry = false;
fCacheLock.WaitLock();
if ( (inFlags & CACHE_ENTRY_TYPE_REPLACE) != 0 || (inFlags & CACHE_ENTRY_TYPE_EXTENDED) != 0 ) {
cache_remove( fCache, inKey ); bAddEntry = true;
} else {
bAddEntry = RemoveCollision( inKey );
}
if ( bAddEntry == true && inTTL > 0 )
{
if ( inTTL > fCacheTTL )
inTTL = fCacheTTL;
out = new sCacheEntry( inTTL, time(NULL), inFlags, inBuffer );
assert( out != NULL );
if ( cache_set_and_retain(fCache, inKey, out, (inBuffer != NULL ? inBuffer->datalen : 0)) == 0 ) {
DbgLog( kLogDebug, "CCache::CreateEntry succeeded for %X", out );
}
else {
DbgLog( kLogError, "CCache::CreateEntry failed for %X", out );
DSRelease( out );
}
}
fCacheLock.SignalLock();
}
return out;
}
void CCache::ReleaseEntry( sCacheEntry *inEntry )
{
if ( inEntry != NULL )
{
DbgLog( kLogDebug, "CCache::ReleaseEntry called for %X", inEntry );
fCacheLock.WaitLock();
cache_release_value( fCache, inEntry );
fCacheLock.SignalLock();
}
}
bool CCache::AddKeyToEntry( sCacheEntry *inEntry, char *inKey, bool inUnique )
{
bool bOut = false;
if ( this != NULL && inEntry != NULL && inKey != NULL)
{
bool bAddEntry = false;
fCacheLock.WaitLock();
if ( (inEntry->fFlags & CACHE_ENTRY_TYPE_REPLACE) != 0 || (inEntry->fFlags & CACHE_ENTRY_TYPE_EXTENDED) != 0 ) {
cache_remove( fCache, inKey ); bAddEntry = true;
} else if ( inUnique == true ) {
bAddEntry = RemoveCollision( inKey );
}
if ( bAddEntry == true || inUnique == false )
{
if ( cache_set_and_retain(fCache, inKey, inEntry, (inEntry->fBuffer != NULL ? inEntry->fBuffer->datalen : 0)) == 0 ) {
cache_release_value( fCache, inEntry );
}
}
fCacheLock.SignalLock();
}
return bOut;
}
kvbuf_t *CCache::Fetch( char *inKey, int32_t *outLowestTTL, uint32_t reqFlags )
{
kvbuf_t *out = NULL;
uint32_t expireFlags = 0;
if ( this != NULL )
{
sCacheEntry *entry = NULL;
time_t now = time( NULL );
int32_t lowestTTL = fCacheTTL;
fCacheLock.WaitLock();
if ( inKey != NULL )
{
if ( cache_get_and_retain(fCache, inKey, (void **)&entry) == 0 )
{
assert( entry != NULL );
if ( entry->Validate(now) == true )
{
if ((fPolicyFlags & CACHE_POLICY_UPDATE_TTL_ON_HIT) != 0 && (entry->fTTL != 0))
entry->fBestBefore = now + entry->fTTL;
if ( reqFlags == 0 || (entry->fFlags & reqFlags) != 0 ) {
entry->fHits++;
entry->fLastAccess = now;
out = kvbuf_new();
kvbuf_append_kvbuf( out, entry->fBuffer );
if ( entry->fTTL < lowestTTL )
lowestTTL = entry->fTTL;
}
cache_release_value( fCache, entry );
}
else
{
expireFlags |= entry->fFlags;
cache_release_value( fCache, entry );
cache_remove( fCache, inKey );
}
}
}
if ( outLowestTTL != NULL )
(*outLowestTTL) = lowestTTL;
DoNotifies( expireFlags );
fCacheLock.SignalLock();
}
return out;
}
struct sSweepInvoke
{
stack<void *> *fRemoveList;
uint32_t fEntryType;
bool fCheckDate;
int fCount;
uint32_t fExpireFlags;
time_t fNow;
};
static void SweepInvoke( void *key, void *value, void *user_data )
{
sSweepInvoke *context = (sSweepInvoke *) user_data;
sCacheEntry *freeItem = (sCacheEntry *) value;
if ( freeItem->fFlags == 0 || (freeItem->fFlags & context->fEntryType) != 0 )
{
if ( ((freeItem->fFlags & CACHE_ENTRY_TYPE_HOST) != 0 || freeItem->fValidation == NULL || freeItem->fValidation->fNodeAvailable) &&
(context->fCheckDate == false || context->fNow >= freeItem->fBestBefore) )
{
context->fExpireFlags |= freeItem->fFlags;
context->fRemoveList->push( key );
context->fCount++;
}
}
}
int CCache::Sweep( uint32_t inEntryType, bool inCheckDate )
{
int iCount = 0;
if ( this != NULL ) {
stack<void *> removeList;
sSweepInvoke context = {
fRemoveList : &removeList,
fEntryType : inEntryType,
fCheckDate : inCheckDate,
fCount : 0,
fExpireFlags : 0,
fNow : time( NULL ),
};
fCacheLock.WaitLock();
cache_invoke( fCache, SweepInvoke, &context );
while ( removeList.size() )
{
cache_remove( fCache, removeList.top() );
removeList.pop();
}
DoNotifies( context.fExpireFlags );
fCacheLock.SignalLock();
iCount = context.fCount;
}
return iCount;
}
struct sAvail
{
CFStringRef fNode;
bool fNewState;
int fCount;
};
static void InvokeUpdateAvail( void *key, void *value, void *user_data )
{
sCacheEntry *entry = (sCacheEntry *) value;
sAvail *context = (sAvail *) user_data;
sCacheValidation *valid_t = entry->fValidation;
if (valid_t != NULL && valid_t->fNode != NULL && context->fNewState != valid_t->fNodeAvailable &&
CFStringCompare(valid_t->fNode, context->fNode, 0) == kCFCompareEqualTo)
{
valid_t->fNodeAvailable = context->fNewState;
context->fCount++;
}
}
int CCache::UpdateAvailability( char *inNode, bool inState )
{
int iCount = 0;
if ( this != NULL && inNode != NULL)
{
sAvail context = {
fNode : CFStringCreateWithCString( kCFAllocatorDefault, inNode, kCFStringEncodingUTF8 ),
fNewState : inState,
fCount : 0
};
fCacheLock.WaitLock();
cache_invoke( fCache, InvokeUpdateAvail, &context );
iCount = context.fCount;
DSCFRelease( context.fNode );
fCacheLock.SignalLock();
}
return iCount;
}
#pragma mark -
#pragma mark Private methods
void CCache::DoNotifies( uint32_t inFlags )
{
if( (inFlags & CACHE_ENTRY_TYPE_GROUP) == CACHE_ENTRY_TYPE_GROUP )
notify_post( "com.apple.system.DirectoryService.InvalidateCache.group" );
if( (inFlags & CACHE_ENTRY_TYPE_HOST) == CACHE_ENTRY_TYPE_HOST )
notify_post( "com.apple.system.DirectoryService.InvalidateCache.host" );
if( (inFlags & CACHE_ENTRY_TYPE_SERVICE) == CACHE_ENTRY_TYPE_SERVICE )
notify_post( "com.apple.system.DirectoryService.InvalidateCache.service" );
if( (inFlags & CACHE_ENTRY_TYPE_USER) == CACHE_ENTRY_TYPE_USER )
notify_post( "com.apple.system.DirectoryService.InvalidateCache.user" );
}
bool CCache::RemoveCollision( char *inKey )
{
bool bOut = true;
if ( this != NULL && inKey != NULL )
{
sCacheEntry *entry = NULL;
if ( cache_get_and_retain(fCache, inKey, (void **)&entry) == 0 )
{
cache_release_value( fCache, entry );
if ( (fPolicyFlags & CACHE_POLICY_REPLACE_ON_COLLISION) != 0 ) {
cache_remove( fCache, inKey );
} else {
bOut = false;
}
}
}
return bOut;
}
void CCache::KeyRetain( void *key_in, void **key_out, void *user_data )
{
*key_out = strdup( (char *) key_in );
}
void CCache::CacheEntryRelease( void *inValue, void *user_data )
{
sCacheEntry *entry = (sCacheEntry *) inValue;
DSRelease( entry );
}