/* vim:set sw=4 ts=8 fileencoding=cp1251::Кодировка:WINDOWS-1251[АБЁЪЯабёъя] */
#ifdef _WIN32
    #pragma setlocale("rus")
#endif
/*
 * Copyright(C) 2000-2014 Проект ИОК
 *
 * Этот файл содержит информацию, являющуюся
 * собственностью компании Крипто Про.
 *
 * Любая часть этого файла не может быть скопирована,
 * исправлена, переведена на другие языки,
 * локализована или модифицирована любым способом,
 * откомпилирована, передана по сети с или на
 * любую компьютерную систему без предварительного
 * заключения соглашения с компанией Крипто Про.
 */

#ifndef STDLOCKS_H_INCLUDED
#define STDLOCKS_H_INCLUDED 1

#include "stdafx.h"
#include "cpclocks.h"
#include "wincspc.h"
#ifdef __cplusplus
#   include "boost/static_assert.hpp"
#else
#   define CP_C_VERSION_BOOST_STATIC_ASSERT(x) { \
		    char cp_c_version_static_assert[(x)?1:-1]; \
		    UNUSED(cp_c_version_static_assert); \
		}
#   define BOOST_STATIC_ASSERT(x)  CP_C_VERSION_BOOST_STATIC_ASSERT(x)
#endif

#ifdef __cplusplus
extern "C" {
#endif

#define slr_cpc_rwlock_push(pS, pfU) \
		support_lckrec_push(cpc_rwlock_imp(pS), \
				    (void (*)(void *))(pfU))

#define slr_cpc_rwlock_pop(pS) \
		support_lckrec_pop(cpc_rwlock_imp(pS))

#if defined(TRACK_LOCKS_USAGE)
#define STRINGIFY_INTERNAL(x) #x
#define STRINGIFY(x) STRINGIFY_INTERNAL(x)
#define rwlock_wrlock(a) ((a)->location = __FILE__ ":" STRINGIFY(__LINE__), rwlock_wrlock_impl((a)))
#define rwlock_rdlock(a) ((a)->location = __FILE__ ":" STRINGIFY(__LINE__), rwlock_rdlock_impl((a)))
#else /*defined(TRACK_LOCKS_USAGE)*/
#define rwlock_wrlock(a) rwlock_wrlock_impl((a))
#define rwlock_rdlock(a) rwlock_rdlock_impl((a))
#endif /*defined(TRACK_LOCKS_USAGE)*/

//===
#if defined(UNIX)
//===

#   include <unistd.h>
	// Документация Apple рекомендует #ifdef _POSIX_THREADS,
	// но нам без pthread.h здесь делать всё равно нечего.
#   include <pthread.h>

#   if !defined(OLD_RWLOCKS) && _POSIX_TIMEOUTS+0 >= 0

#       include <time.h>
#       include <sys/time.h>

#       define UNIX_RWLOCK_TIMEOUT 10

typedef struct 
{
    LONG volatile lvStarved;
    pthread_rwlock_t Lock;
} CPUnixRWLock, *PCPUnixRWLock;

static inline void CPUnixRWLockGetClock(struct timespec * pts)
{
#ifdef RWLOCK_USE_CLOCK_GETTIME
    clock_gettime(CLOCK_REALTIME,pts);
#else
    struct timeval tm;
    gettimeofday(&tm,NULL);
    pts->tv_sec=tm.tv_sec;
    pts->tv_nsec=tm.tv_usec*1000L;
#endif
}

static inline void CPUnixRWLockInit(CPUnixRWLock * plock)
{
    // "умный" gcc 4.4.7 увидел strict aliasing
    (*plock).lvStarved = 0;
    pthread_rwlock_init(&plock->Lock,NULL);
}

static inline void CPUnixRWLockDestroy(PCPUnixRWLock plock)
{
    pthread_rwlock_destroy(&plock->Lock);
}

static inline void CPUnixRWLockWRLock(PCPUnixRWLock plock)
{
    struct timespec ts;
    if ( pthread_rwlock_trywrlock(&plock->Lock) == 0)
	return;
    CPUnixRWLockGetClock(&ts);
    ts.tv_sec+=UNIX_RWLOCK_TIMEOUT;
    if ( pthread_rwlock_timedwrlock(&plock->Lock,&ts) == 0)
	return;
    support_interlocked_increment(&plock->lvStarved);	
    pthread_rwlock_wrlock(&plock->Lock);
    support_interlocked_decrement(&plock->lvStarved);
}

static inline void CPUnixRWLockRDLock(PCPUnixRWLock plock)
{
    struct timespec ts;
    if (support_interlocked_exchange_add(&plock->lvStarved,0) <= 0)
    {
	if ( pthread_rwlock_tryrdlock(&plock->Lock) == 0)
	    return;
	CPUnixRWLockGetClock(&ts);
	ts.tv_sec+=UNIX_RWLOCK_TIMEOUT;
	if ( pthread_rwlock_timedrdlock(&plock->Lock,&ts) == 0)
	    return; 
    }
    CPUnixRWLockWRLock(plock);	
}

static inline void CPUnixRWLockUnlock(PCPUnixRWLock plock)
{
    pthread_rwlock_unlock(&plock->Lock);
}

static inline PCPUnixRWLock 
cpc_rwlock_imp(LPCPC_RWLOCK pSection)
{
    return (PCPUnixRWLock)pSection;
}

static inline DWORD CPCAPI
rwlock_init(LPCPC_RWLOCK pSection, DWORD cbSection, LPVOID lpArg)
{
    UNUSED(lpArg);

    if((sizeof(CPUnixRWLock) > cbSection) |
       (sizeof(CPUnixRWLock) > sizeof(pSection->dummy))) {
	return (DWORD)NTE_NO_MEMORY;
    }
    BOOST_STATIC_ASSERT(sizeof(CPUnixRWLock) <= sizeof(pSection->dummy));
    CPUnixRWLockInit(cpc_rwlock_imp(pSection));

    return S_OK;
}

static inline VOID CPCAPI
rwlock_destroy(LPCPC_RWLOCK pSection)
{
    CPUnixRWLockDestroy(cpc_rwlock_imp(pSection));
}

static inline VOID CPCAPI
rwlock_wrlock_impl(LPCPC_RWLOCK pSection)
{
    slr_cpc_rwlock_push(pSection, CPUnixRWLockUnlock);
    CPUnixRWLockWRLock(cpc_rwlock_imp(pSection));
}

static inline VOID CPCAPI
rwlock_rdlock_impl(LPCPC_RWLOCK pSection)
{
    slr_cpc_rwlock_push(pSection, CPUnixRWLockUnlock);
    CPUnixRWLockRDLock(cpc_rwlock_imp(pSection));
}

static inline VOID CPCAPI
rwlock_unlock(LPCPC_RWLOCK pSection)
{
    CPUnixRWLockUnlock(cpc_rwlock_imp(pSection));
    slr_cpc_rwlock_pop(pSection);
}

#   else /* OLD_RWLOCKS */

#       if !defined(IOS) && !defined(DARWIN)
#           error "TODO:XXXX remove: OLD_RWLOCK only for iOS/Mac OS X"
#       endif

static inline pthread_rwlock_t *
cpc_rwlock_imp(LPCPC_RWLOCK pSection)
{
    return (pthread_rwlock_t *)pSection->dummy;
}

static inline DWORD CPCAPI
rwlock_init(LPCPC_RWLOCK pSection, DWORD cbSection, LPVOID lpArg)
{
    UNUSED(lpArg);

    if((sizeof(pthread_rwlock_t) > cbSection) |
       (sizeof(pthread_rwlock_t) > sizeof(pSection->dummy))) {
	return (DWORD)NTE_NO_MEMORY;
    }
    BOOST_STATIC_ASSERT(sizeof(pthread_rwlock_t) <= sizeof(pSection->dummy));
    return pthread_rwlock_init(cpc_rwlock_imp(pSection), NULL);
}

static inline VOID CPCAPI
rwlock_destroy(LPCPC_RWLOCK pSection)
{
    pthread_rwlock_destroy(cpc_rwlock_imp(pSection));
}

static inline VOID CPCAPI
rwlock_wrlock_impl(LPCPC_RWLOCK pSection)
{
    slr_cpc_rwlock_push(pSection, pthread_rwlock_unlock);
    pthread_rwlock_wrlock(cpc_rwlock_imp(pSection));
}

static inline VOID CPCAPI
rwlock_rdlock_impl(LPCPC_RWLOCK pSection)
{
    slr_cpc_rwlock_push(pSection, pthread_rwlock_unlock);
    pthread_rwlock_rdlock(cpc_rwlock_imp(pSection));
}

static inline VOID CPCAPI
rwlock_unlock(LPCPC_RWLOCK pSection)
{
    pthread_rwlock_unlock(cpc_rwlock_imp(pSection));
    slr_cpc_rwlock_pop(pSection);
}
#   endif /*OLD_RWLOCKS*/

//===
#elif defined(KSP_LITE)
//===

static __inline PFAST_MUTEX
ksp_fast_mutex(LPCPC_RWLOCK pSection)
{
    return (PFAST_MUTEX)pSection->dummy;
}

static __inline DWORD CPCAPI
rwlock_init(LPCPC_RWLOCK pSection, DWORD cbSection, LPVOID lpArg)
{
    if((sizeof(FAST_MUTEX) > cbSection) |
       (sizeof(FAST_MUTEX) > sizeof(pSection->dummy))) {
	return (DWORD)NTE_NO_MEMORY;
    }
    UNUSED (lpArg);
    BOOST_STATIC_ASSERT(sizeof(FAST_MUTEX) <= sizeof(pSection->dummy));
    ExInitializeFastMutex(ksp_fast_mutex(pSection));
    return S_OK;
}

static __inline VOID CPCAPI
rwlock_destroy(LPCPC_RWLOCK pSection)
{
    UNUSED (pSection);
}

static __inline VOID CPCAPI
rwlock_wrlock_impl(LPCPC_RWLOCK pSection)
{
    // XTODO: hi
    ExAcquireFastMutex(ksp_fast_mutex(pSection));
}

static __inline VOID CPCAPI
rwlock_rdlock_impl(LPCPC_RWLOCK pSection)
{
    // XTODO: hi
    ExAcquireFastMutex(ksp_fast_mutex(pSection));
}

static __inline VOID CPCAPI
rwlock_unlock(LPCPC_RWLOCK pSection)
{
    ExReleaseFastMutex(ksp_fast_mutex(pSection));
}

//===
#else
//===

static __inline CRWLock *
cpc_rwlock_imp(LPCPC_RWLOCK pSection)
{
    return (CRWLock *)pSection->dummy;
}

static __inline VOID CPCAPI
rwlock_unlock_imp(CRWLock *pC)
{
    CRWLock_Release(pC, CRWLOCK_ANY);
}

#if !(defined(CSP_LITE) && defined(_WIN32))
static __inline DWORD CPCAPI
rwlock_init(LPCPC_RWLOCK pSection, DWORD cbSection, LPVOID lpArg)
{
    if((sizeof(CRWLock) > cbSection) |
       (sizeof(CRWLock) > sizeof(pSection->dummy))) {
	return (DWORD)NTE_NO_MEMORY;
    }
    BOOST_STATIC_ASSERT(sizeof(CRWLock) <= sizeof(pSection->dummy));

    if (CRWLock_Initialize(cpc_rwlock_imp(pSection), lpArg))
	return S_OK;
    return (DWORD)NTE_NO_MEMORY;
}
#endif

static __inline VOID CPCAPI
rwlock_destroy(LPCPC_RWLOCK pSection)
{
    CRWLock_Cleanup(cpc_rwlock_imp(pSection));
}

static __inline VOID CPCAPI
rwlock_wrlock_impl(LPCPC_RWLOCK pSection)
{
    slr_cpc_rwlock_push(pSection, rwlock_unlock_imp);
    CRWLock_Claim(cpc_rwlock_imp(pSection), CRWLOCK_WRITE);
}

static __inline VOID CPCAPI
rwlock_rdlock_impl(LPCPC_RWLOCK pSection)
{
    slr_cpc_rwlock_push(pSection, rwlock_unlock_imp);
    CRWLock_Claim(cpc_rwlock_imp(pSection), CRWLOCK_READ);
}

static __inline VOID CPCAPI
rwlock_unlock(LPCPC_RWLOCK pSection)
{
    rwlock_unlock_imp(cpc_rwlock_imp(pSection));
    slr_cpc_rwlock_pop(pSection);
}
#endif

#ifdef __cplusplus
}
#endif

#define SET_LOCKS(cfg) {\
 (cfg).lockFuncs.rwlock_init=rwlock_init;\
 (cfg).lockFuncs.rwlock_destroy=rwlock_destroy;\
 (cfg).lockFuncs.rwlock_wrlock=rwlock_wrlock_impl;\
 (cfg).lockFuncs.rwlock_rdlock=rwlock_rdlock_impl;\
 (cfg).lockFuncs.rwlock_unlock=rwlock_unlock;}
 
#endif /* STDLOCKS_H_INCLUDED */
