/*****************************************************************************/ // Copyright 2002-2008 Adobe Systems Incorporated // All Rights Reserved. // // NOTICE: Adobe permits you to use, modify, and distribute this file in // accordance with the terms of the Adobe license agreement accompanying it. /*****************************************************************************/ /* $Id: //mondo/dng_sdk_1_4/dng_sdk/source/dng_pthread.cpp#2 $ */ /* $DateTime: 2012/07/31 22:04:34 $ */ /* $Change: 840853 $ */ /* $Author: tknoll $ */ #include "dng_pthread.h" /*****************************************************************************/ #if qDNGThreadSafe /*****************************************************************************/ #include "dng_assertions.h" /*****************************************************************************/ #if qWinOS #pragma warning(disable : 4786) // Nothing in this file requires Unicode, // However, CreateSemaphore has a path parameter // (which is NULL always in this code) and thus // does not work on Win98 if UNICODE is defined. // So we force it off here. #undef UNICODE #undef _UNICODE #include <windows.h> #include <process.h> #include <errno.h> #include <memory> #include <new> #include <map> #else #include <sys/time.h> #endif /*****************************************************************************/ #if qWinOS /*****************************************************************************/ namespace { struct waiter { struct waiter *prev; struct waiter *next; HANDLE semaphore; bool chosen_by_signal; }; } /*****************************************************************************/ struct dng_pthread_mutex_impl { CRITICAL_SECTION lock; dng_pthread_mutex_impl() { ::InitializeCriticalSection(&lock); } ~dng_pthread_mutex_impl() { ::DeleteCriticalSection(&lock); } void Lock() { ::EnterCriticalSection(&lock); } void Unlock() { ::LeaveCriticalSection(&lock); } private: dng_pthread_mutex_impl &operator=(const dng_pthread_mutex_impl &) { } dng_pthread_mutex_impl(const dng_pthread_mutex_impl &) { } }; /*****************************************************************************/ struct dng_pthread_cond_impl { dng_pthread_mutex_impl lock; // Mutual exclusion on next two variables waiter *head_waiter; // List of threads waiting on this condition waiter *tail_waiter; // Used to get FIFO, rather than LIFO, behavior for pthread_cond_signal unsigned int broadcast_generation; // Used as sort of a separator on broadcasts // saves having to walk the waiters list setting // each one's "chosen_by_signal" flag while the condition is locked dng_pthread_cond_impl() : head_waiter(NULL), tail_waiter(NULL), broadcast_generation(0) { } ~dng_pthread_cond_impl() { } ; // Non copyable private: dng_pthread_cond_impl &operator=(const dng_pthread_cond_impl &) { } dng_pthread_cond_impl(const dng_pthread_cond_impl &) { } }; /*****************************************************************************/ namespace { struct ScopedLock { dng_pthread_mutex_impl *mutex; ScopedLock(dng_pthread_mutex_impl *arg) : mutex(arg) { mutex->Lock(); } ScopedLock(dng_pthread_mutex_impl &arg) : mutex(&arg) { mutex->Lock(); } ~ScopedLock() { mutex->Unlock(); } private: ScopedLock &operator=(const ScopedLock &) { } ScopedLock(const ScopedLock &) { } }; dng_pthread_mutex_impl validationLock; void ValidateMutex(dng_pthread_mutex_t *mutex) { if (*mutex != DNG_PTHREAD_MUTEX_INITIALIZER) return; ScopedLock lock(validationLock); if (*mutex == DNG_PTHREAD_MUTEX_INITIALIZER) dng_pthread_mutex_init(mutex, NULL); } void ValidateCond(dng_pthread_cond_t *cond) { if (*cond != DNG_PTHREAD_COND_INITIALIZER) return; ScopedLock lock(validationLock); if (*cond == DNG_PTHREAD_COND_INITIALIZER) dng_pthread_cond_init(cond, NULL); } DWORD thread_wait_sema_TLS_index; bool thread_wait_sema_inited = false; dng_pthread_once_t once_thread_TLS = DNG_PTHREAD_ONCE_INIT; void init_thread_TLS() { thread_wait_sema_TLS_index = ::TlsAlloc(); thread_wait_sema_inited = true; } void finalize_thread_TLS() { if (thread_wait_sema_inited) { ::TlsFree(thread_wait_sema_TLS_index); thread_wait_sema_inited = false; } } dng_pthread_mutex_impl primaryHandleMapLock; typedef std::map<DWORD, std::pair<HANDLE, void **> > ThreadMapType; // A map to make sure handles are freed and to allow returning a pointer sized result // even on 64-bit Windows. ThreadMapType primaryHandleMap; HANDLE GetThreadSemaphore() { dng_pthread_once(&once_thread_TLS, init_thread_TLS); HANDLE semaphore = ::TlsGetValue(thread_wait_sema_TLS_index); if (semaphore == NULL) { semaphore = ::CreateSemaphore(NULL, 0, 1, NULL); ::TlsSetValue(thread_wait_sema_TLS_index, semaphore); } return semaphore; } void FreeThreadSemaphore() { if (thread_wait_sema_inited) { HANDLE semaphore = (HANDLE)::TlsGetValue(thread_wait_sema_TLS_index); if (semaphore != NULL) { ::TlsSetValue(thread_wait_sema_TLS_index, NULL); ::CloseHandle(semaphore); } } } struct trampoline_args { void *(*func)(void *); void *arg; }; // This trampoline takes care of the return type being different // between pthreads thread funcs and Windows C lib thread funcs unsigned __stdcall trampoline(void *arg_arg) { trampoline_args *args_ptr = (trampoline_args *)arg_arg; trampoline_args args = *args_ptr; delete args_ptr; GetThreadSemaphore(); void *result = args.func(args.arg); { ScopedLock lockMap(primaryHandleMapLock); ThreadMapType::iterator iter = primaryHandleMap.find(pthread_self()); if (iter != primaryHandleMap.end()) *iter->second.second = result; } FreeThreadSemaphore(); return S_OK; } } /*****************************************************************************/ extern "C" { /*****************************************************************************/ struct dng_pthread_attr_impl { size_t stacksize; }; /*****************************************************************************/ int dng_pthread_attr_init(pthread_attr_t *attr) { dng_pthread_attr_impl *newAttrs; newAttrs = new (std::nothrow) dng_pthread_attr_impl; if (newAttrs == NULL) return -1; // ENOMEM; newAttrs->stacksize = 0; *attr = newAttrs; return 0; } /*****************************************************************************/ int dng_pthread_attr_destroy(pthread_attr_t *attr) { if (*attr == NULL) return -1; // EINVAL delete *attr; *attr = NULL; return 0; } /*****************************************************************************/ int dng_pthread_attr_setstacksize(dng_pthread_attr_t *attr, size_t stacksize) { if (attr == NULL || (*attr) == NULL) return -1; // EINVAL (*attr)->stacksize = stacksize; return 0; } /*****************************************************************************/ int dng_pthread_attr_getstacksize(const dng_pthread_attr_t *attr, size_t *stacksize) { if (attr == NULL || (*attr) == NULL || stacksize == NULL) return -1; // EINVAL *stacksize = (*attr)->stacksize; return 0; } /*****************************************************************************/ int dng_pthread_create(dng_pthread_t *thread, const pthread_attr_t *attrs, void * (*func)(void *), void *arg) { try { uintptr_t result; unsigned threadID; std::auto_ptr<trampoline_args> args(new (std::nothrow) trampoline_args); std::auto_ptr<void *> resultHolder(new (std::nothrow) (void *)); if (args.get() == NULL || resultHolder.get () == NULL) return -1; // ENOMEM args->func = func; args->arg = arg; size_t stacksize = 0; if (attrs != NULL) dng_pthread_attr_getstacksize (attrs, &stacksize); { ScopedLock lockMap(primaryHandleMapLock); result = _beginthreadex(NULL, (unsigned)stacksize, trampoline, args.get(), 0, &threadID); if (result == NULL) return -1; // ENOMEM args.release(); std::pair<DWORD, std::pair<HANDLE, void **> > newMapEntry(threadID, std::pair<HANDLE, void **>((HANDLE)result, resultHolder.get ())); std::pair<ThreadMapType::iterator, bool> insertion = primaryHandleMap.insert(newMapEntry); // If there is a handle open on the thread, its ID should not be reused so assert that an insertion was made. DNG_ASSERT(insertion.second, "pthread emulation logic error"); } resultHolder.release (); *thread = (dng_pthread_t)threadID; return 0; } catch (const std::bad_alloc &) { return -1; } } /*****************************************************************************/ int dng_pthread_detach(dng_pthread_t thread) { HANDLE primaryHandle; void **resultHolder = NULL; { ScopedLock lockMap(primaryHandleMapLock); ThreadMapType::iterator iter = primaryHandleMap.find(thread); if (iter == primaryHandleMap.end()) return -1; primaryHandle = iter->second.first; // A join is waiting on the thread. if (primaryHandle == NULL) return -1; resultHolder = iter->second.second; primaryHandleMap.erase(iter); } delete resultHolder; if (!::CloseHandle(primaryHandle)) return -1; return 0; } /*****************************************************************************/ int dng_pthread_join(dng_pthread_t thread, void **result) { bool found = false; HANDLE primaryHandle = NULL; void **resultHolder = NULL; ThreadMapType::iterator iter; { ScopedLock lockMap(primaryHandleMapLock); iter = primaryHandleMap.find(thread); found = iter != primaryHandleMap.end(); if (found) { primaryHandle = iter->second.first; resultHolder = iter->second.second; // Set HANDLE to NULL to force any later join or detach to fail. iter->second.first = NULL; } } // This case can happens when joining a thread not created with pthread_create, // which is a bad idea, but it gets mapped to doing the join, but always returns NULL. if (!found) primaryHandle = ::OpenThread(SYNCHRONIZE|THREAD_QUERY_INFORMATION, FALSE, thread); if (primaryHandle == NULL) return -1; DWORD err; if (::WaitForSingleObject(primaryHandle, INFINITE) != WAIT_OBJECT_0) { err = ::GetLastError(); return -1; } { ScopedLock lockMap(primaryHandleMapLock); if (iter != primaryHandleMap.end()) primaryHandleMap.erase(iter); } ::CloseHandle(primaryHandle); if (result != NULL && resultHolder != NULL) *result = *resultHolder; delete resultHolder; return 0; } /*****************************************************************************/ dng_pthread_t dng_pthread_self() { return (dng_pthread_t)::GetCurrentThreadId(); } /*****************************************************************************/ void dng_pthread_exit(void *result) { { ScopedLock lockMap(primaryHandleMapLock); ThreadMapType::iterator iter = primaryHandleMap.find(pthread_self()); if (iter != primaryHandleMap.end()) *iter->second.second = result; } FreeThreadSemaphore(); _endthreadex(S_OK); } /*****************************************************************************/ int dng_pthread_mutex_init(dng_pthread_mutex_t *mutex, void * /* attrs */) { dng_pthread_mutex_t result; try { result = new(dng_pthread_mutex_impl); } catch (const std::bad_alloc &) { return -1; } if (result == NULL) return -1; *mutex = result; return 0; } /*****************************************************************************/ int dng_pthread_mutex_destroy(dng_pthread_mutex_t *mutex) { if (*mutex == DNG_PTHREAD_MUTEX_INITIALIZER) { *mutex = NULL; return 0; } delete *mutex; *mutex = NULL; return 0; } /*****************************************************************************/ int dng_pthread_cond_init(dng_pthread_cond_t *cond, void * /* attrs */) { dng_pthread_cond_t result; try { result = new(dng_pthread_cond_impl); } catch (const std::bad_alloc &) { return -1; } if (result == NULL) return -1; *cond = result; return 0; } /*****************************************************************************/ int dng_pthread_cond_destroy(dng_pthread_cond_t *cond) { if (*cond == DNG_PTHREAD_COND_INITIALIZER) { *cond = NULL; return 0; } delete *cond; *cond = NULL; return 0; } /*****************************************************************************/ int dng_pthread_mutexattr_init(dng_pthread_mutexattr_t* mutexattr) { return 0; } /*****************************************************************************/ int dng_pthread_mutexattr_settype(dng_pthread_mutexattr_t* mutexattr, int type) { return 0; } /*****************************************************************************/ int dng_pthread_mutex_lock(dng_pthread_mutex_t *mutex) { ValidateMutex(mutex); (*mutex)->Lock(); return 0; } /*****************************************************************************/ int dng_pthread_mutex_unlock(dng_pthread_mutex_t *mutex) { ValidateMutex(mutex); (*mutex)->Unlock(); return 0; } /*****************************************************************************/ static int cond_wait_internal(dng_pthread_cond_t *cond, dng_pthread_mutex_t *mutex, int timeout_milliseconds) { dng_pthread_cond_impl &real_cond = **cond; dng_pthread_mutex_impl &real_mutex = **mutex; waiter this_wait; HANDLE semaphore = GetThreadSemaphore(); int my_generation; // The broadcast generation this waiter is in { this_wait.next = NULL; this_wait.semaphore = semaphore; this_wait.chosen_by_signal = 0; ScopedLock lock1(real_cond.lock); // Add this waiter to the end of the list. this_wait.prev = real_cond.tail_waiter; if (real_cond.tail_waiter != NULL) real_cond.tail_waiter->next = &this_wait; real_cond.tail_waiter = &this_wait; // If the list was empty, set the head of the list to this waiter. if (real_cond.head_waiter == NULL) real_cond.head_waiter = &this_wait; // Note which broadcast generation this waiter belongs to. my_generation = real_cond.broadcast_generation; } real_mutex.Unlock(); DWORD result = ::WaitForSingleObject(semaphore, timeout_milliseconds); if (result == WAIT_TIMEOUT) { // If the wait timed out, this thread is likely still on the waiters list // of the condition. However, there is a race in that the thread may have been // signaled or broadcast between when WaitForSingleObject decided // we had timed out and this code running. bool mustConsumeSemaphore = false; { ScopedLock lock2(real_cond.lock); bool chosen_by_signal = this_wait.chosen_by_signal; bool chosen_by_broadcast = my_generation != real_cond.broadcast_generation; if (chosen_by_signal || chosen_by_broadcast) mustConsumeSemaphore = true; else { // Still on waiters list. Remove this waiter from list. if (this_wait.next != NULL) this_wait.next->prev = this_wait.prev; else real_cond.tail_waiter = this_wait.prev; if (this_wait.prev != NULL) this_wait.prev->next = this_wait.next; else real_cond.head_waiter = this_wait.next; } } if (mustConsumeSemaphore) { ::WaitForSingleObject(semaphore, INFINITE); result = WAIT_OBJECT_0; } } else DNG_ASSERT (result == WAIT_OBJECT_0, "pthread emulation logic error"); // reacquire the mutex real_mutex.Lock(); return (result == WAIT_TIMEOUT) ? DNG_ETIMEDOUT : 0; } /*****************************************************************************/ int dng_pthread_cond_wait(dng_pthread_cond_t *cond, dng_pthread_mutex_t *mutex) { ValidateCond(cond); return cond_wait_internal(cond, mutex, INFINITE); } /*****************************************************************************/ int dng_pthread_cond_timedwait(dng_pthread_cond_t *cond, dng_pthread_mutex_t *mutex, struct dng_timespec *latest_time) { ValidateCond(cond); struct dng_timespec sys_timespec; dng_pthread_now (&sys_timespec); __int64 sys_time = (__int64)sys_timespec.tv_sec * 1000000000 + sys_timespec.tv_nsec; __int64 lock_time = (__int64)latest_time->tv_sec * 1000000000 + latest_time->tv_nsec; int wait_millisecs = (int)((lock_time - sys_time + 500000) / 1000000); if (wait_millisecs < 0) wait_millisecs = 0; return cond_wait_internal(cond, mutex, wait_millisecs); } /*****************************************************************************/ int dng_pthread_cond_signal(dng_pthread_cond_t *cond) { ValidateCond(cond); waiter *first; dng_pthread_cond_impl &real_cond = **cond; { ScopedLock lock(real_cond.lock); first = real_cond.head_waiter; if (first != NULL) { if (first->next != NULL) first->next->prev = NULL; else real_cond.tail_waiter = NULL; // Or first->prev, which is always NULL in this case first->chosen_by_signal = true; real_cond.head_waiter = first->next; } } if (first != NULL) ::ReleaseSemaphore(first->semaphore, 1, NULL); return 0; } /*****************************************************************************/ int dng_pthread_cond_broadcast(dng_pthread_cond_t *cond) { ValidateCond(cond); waiter *first; dng_pthread_cond_impl &real_cond = **cond; { ScopedLock lock(real_cond.lock); first = real_cond.head_waiter; real_cond.head_waiter = NULL; real_cond.tail_waiter = NULL; real_cond.broadcast_generation++; } while (first != NULL) { waiter *next = first->next; ::ReleaseSemaphore(first->semaphore, 1, NULL); first = next; } return 0; } /*****************************************************************************/ int dng_pthread_once(dng_pthread_once_t *once, void (*init_func)()) { if (once == NULL || init_func == NULL) return EINVAL; if (once->inited) return 0; if (::InterlockedIncrement(&once->semaphore) == 0) { init_func(); once->inited = 1; } else { while (!once->inited) Sleep(0); } return 0; } /*****************************************************************************/ int dng_pthread_key_create(dng_pthread_key_t * key, void (*destructor) (void *)) { if (destructor != NULL) return -1; DWORD result = ::TlsAlloc(); if (result == TLS_OUT_OF_INDEXES) return -1; *key = (unsigned long)result; return 0; } /*****************************************************************************/ int dng_pthread_key_delete(dng_pthread_key_t key) { if (::TlsFree((DWORD)key)) return 0; return -1; } /*****************************************************************************/ int dng_pthread_setspecific(dng_pthread_key_t key, const void *value) { if (::TlsSetValue((DWORD)key, const_cast<void *>(value))) return 0; return -1; } /*****************************************************************************/ void *dng_pthread_getspecific(dng_pthread_key_t key) { return ::TlsGetValue((DWORD)key); } /*****************************************************************************/ namespace { struct rw_waiter { struct rw_waiter *prev; struct rw_waiter *next; HANDLE semaphore; bool is_writer; }; } struct dng_pthread_rwlock_impl { dng_pthread_mutex_impl mutex; rw_waiter *head_waiter; rw_waiter *tail_waiter; unsigned long readers_active; unsigned long writers_waiting; bool writer_active; dng_pthread_cond_impl read_wait; dng_pthread_cond_impl write_wait; dng_pthread_rwlock_impl () : mutex () , head_waiter (NULL) , tail_waiter (NULL) , readers_active (0) , writers_waiting (0) , read_wait () , write_wait () , writer_active (false) { } ~dng_pthread_rwlock_impl () { } void WakeHeadWaiter () { HANDLE semaphore = head_waiter->semaphore; head_waiter = head_waiter->next; if (head_waiter == NULL) tail_waiter = NULL; ::ReleaseSemaphore(semaphore, 1, NULL); } }; /*****************************************************************************/ int dng_pthread_rwlock_init(dng_pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attrs) { dng_pthread_rwlock_impl *newRWLock; newRWLock = new (std::nothrow) dng_pthread_rwlock_impl; if (newRWLock == NULL) return -1; // ENOMEM; *rwlock = newRWLock; return 0; } /*****************************************************************************/ int dng_pthread_rwlock_destroy(dng_pthread_rwlock_t *rwlock) { dng_pthread_rwlock_impl &real_rwlock = **rwlock; { ScopedLock lock (real_rwlock.mutex); if (real_rwlock.head_waiter != NULL || real_rwlock.readers_active != 0 || real_rwlock.writers_waiting != 0 || real_rwlock.writer_active) return -1; // EBUSY } delete *rwlock; *rwlock = NULL; return 0; } /*****************************************************************************/ #define CHECK_RWLOCK_STATE(real_rwlock) \ DNG_ASSERT (!real_rwlock.writer_active || real_rwlock.readers_active == 0, "dng_pthread_rwlock_t logic error") /*****************************************************************************/ int dng_pthread_rwlock_rdlock(dng_pthread_rwlock_t *rwlock) { dng_pthread_rwlock_impl &real_rwlock = **rwlock; struct rw_waiter this_wait; bool doWait = false;; int result = 0; HANDLE semaphore=NULL; { ScopedLock lock (real_rwlock.mutex); CHECK_RWLOCK_STATE (real_rwlock); if (real_rwlock.writers_waiting > 0 || real_rwlock.writer_active) { semaphore = GetThreadSemaphore(); this_wait.next = NULL; this_wait.semaphore = semaphore; this_wait.is_writer = false; // Add this waiter to the end of the list. this_wait.prev = real_rwlock.tail_waiter; if (real_rwlock.tail_waiter != NULL) real_rwlock.tail_waiter->next = &this_wait; real_rwlock.tail_waiter = &this_wait; // If the list was empty, set the head of the list to this waiter. if (real_rwlock.head_waiter == NULL) real_rwlock.head_waiter = &this_wait; doWait = true; } else real_rwlock.readers_active++; } if (result == 0 && doWait) result = (WaitForSingleObject(semaphore, INFINITE) == WAIT_OBJECT_0) ? 0 : -1; return result; } /*****************************************************************************/ int dng_pthread_rwlock_tryrdlock(dng_pthread_rwlock_t *rwlock) { dng_pthread_rwlock_impl &real_rwlock = **rwlock; ScopedLock lock (real_rwlock.mutex); CHECK_RWLOCK_STATE (real_rwlock); if (real_rwlock.writers_waiting == 0 && !real_rwlock.writer_active) { real_rwlock.readers_active++; return 0; } return -1; } /*****************************************************************************/ int dng_pthread_rwlock_trywrlock(dng_pthread_rwlock_t *rwlock) { dng_pthread_rwlock_impl &real_rwlock = **rwlock; ScopedLock lock (real_rwlock.mutex); CHECK_RWLOCK_STATE (real_rwlock); if (real_rwlock.readers_active == 0 && real_rwlock.writers_waiting == 0 && !real_rwlock.writer_active) { real_rwlock.writer_active = true; return 0; } return -1; } /*****************************************************************************/ int dng_pthread_rwlock_unlock(dng_pthread_rwlock_t *rwlock) { dng_pthread_rwlock_impl &real_rwlock = **rwlock; int result = 0; ScopedLock lock (real_rwlock.mutex); CHECK_RWLOCK_STATE (real_rwlock); if (real_rwlock.readers_active > 0) --real_rwlock.readers_active; else real_rwlock.writer_active = false; while (real_rwlock.head_waiter != NULL) { if (real_rwlock.head_waiter->is_writer) { if (real_rwlock.readers_active == 0) { real_rwlock.writers_waiting--; real_rwlock.writer_active = true; real_rwlock.WakeHeadWaiter (); } break; } else { ++real_rwlock.readers_active; real_rwlock.WakeHeadWaiter (); } } return result; } /*****************************************************************************/ int dng_pthread_rwlock_wrlock(dng_pthread_rwlock_t *rwlock) { dng_pthread_rwlock_impl &real_rwlock = **rwlock; int result = 0; struct rw_waiter this_wait; HANDLE semaphore=NULL; bool doWait = false; { ScopedLock lock (real_rwlock.mutex); CHECK_RWLOCK_STATE (real_rwlock); if (real_rwlock.readers_active || real_rwlock.writers_waiting || real_rwlock.writer_active) { semaphore = GetThreadSemaphore(); this_wait.next = NULL; this_wait.semaphore = semaphore; this_wait.is_writer = true; // Add this waiter to the end of the list. this_wait.prev = real_rwlock.tail_waiter; if (real_rwlock.tail_waiter != NULL) real_rwlock.tail_waiter->next = &this_wait; real_rwlock.tail_waiter = &this_wait; // If the list was empty, set the head of the list to this waiter. if (real_rwlock.head_waiter == NULL) real_rwlock.head_waiter = &this_wait; real_rwlock.writers_waiting++; doWait = true; } else real_rwlock.writer_active = true; } if (result == 0 && doWait) result = (WaitForSingleObject(semaphore, INFINITE) == WAIT_OBJECT_0) ? 0 : -1; return result; } /*****************************************************************************/ void dng_pthread_disassociate() { FreeThreadSemaphore(); } void dng_pthread_terminate() { finalize_thread_TLS(); } /*****************************************************************************/ } // extern "C" /*****************************************************************************/ #endif /*****************************************************************************/ int dng_pthread_now (struct timespec *now) { if (now == NULL) return -1; // EINVAL #if qWinOS FILETIME ft; ::GetSystemTimeAsFileTime(&ft); __int64 sys_time = ((__int64)ft.dwHighDateTime << 32) + ft.dwLowDateTime; #define SecsFrom1601To1970 11644473600 sys_time -= SecsFrom1601To1970 * 10000000LL; sys_time *= 100; // Convert from 100ns to 1ns units now->tv_sec = (long)(sys_time / 1000000000); now->tv_nsec = (long)(sys_time % 1000000000); #else struct timeval tv; if (gettimeofday (&tv, NULL) != 0) return errno; now->tv_sec = tv.tv_sec; now->tv_nsec = tv.tv_usec * 1000; #endif return 0; } /*****************************************************************************/ #endif // qDNGThreadSafe /*****************************************************************************/