/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <sal/config.h>

#include <cassert>
#include <functional>
#include <mutex>
#include <unistd.h>

#include "system.hxx"
#include "unixerrnostring.hxx"
#include <thread_internal.hxx>

#include <string.h>
#if defined(OPENBSD)
#include <sched.h>
#endif
#ifdef __FreeBSD__
#if __FreeBSD_version <= 1201517
#include <pthread_np.h>
#define pthread_setname_np pthread_set_name_np
#endif
#endif
#include <config_options.h>
#include <o3tl/safeint.hxx>
#include <osl/thread.h>
#include <osl/nlsupport.h>
#include <rtl/textenc.h>
#include <sal/log.hxx>
#include <sal/macros.h>
#ifdef ANDROID
#include <jni.h>
#include <android/log.h>
#include <osl/detail/android-bootstrap.h>
#endif

#if defined LINUX && ! defined __FreeBSD_kernel__
#include <sys/syscall.h>
#endif

/****************************************************************************
 * @@@ TODO @@@
 *
 * (1) 'osl_thread_priority_init_Impl()'
 *     - insane assumption that initializing caller is main thread
 *     - use _POSIX_THREAD_PRIORITY_SCHEDULING, not NO_PTHREAD_PRIORITY (?)
 *     - POSIX doesn't require defined prio's for SCHED_OTHER (!)
 *     - use SCHED_RR instead of SCHED_OTHER for defined behaviour (?)
 * (2) 'oslThreadIdentifier' and '{insert|remove|lookup}ThreadId()'
 *     - cannot reliably be applied to 'alien' threads;
 *     - memory leak for 'alien' thread 'HashEntry's;
 *     - use 'reinterpret_cast<unsigned long>(pthread_t)' as identifier
 *       instead (?)
 *     - if yes, change 'oslThreadIdentifier' to 'intptr_t' or similar
 * (3) 'oslSigAlarmHandler()' (#71232#)
 *     - [Under Solaris we get SIGALRM in e.g. pthread_join which terminates
 *       the process. So we initialize our signal handling module and do
 *       register a SIGALRM Handler which catches and ignores it]
 *     - should this still happen, 'signal.c' needs to be fixed instead.
 *
 ****************************************************************************/

#define THREADIMPL_FLAGS_TERMINATE  0x00001
#define THREADIMPL_FLAGS_STARTUP    0x00002
#define THREADIMPL_FLAGS_SUSPENDED  0x00004
#define THREADIMPL_FLAGS_ACTIVE     0x00008
#define THREADIMPL_FLAGS_ATTACHED   0x00010
#define THREADIMPL_FLAGS_DESTROYED  0x00020

namespace {

typedef struct osl_thread_impl_st
{
    pthread_t           m_hThread;
    oslThreadIdentifier m_Ident; /* @@@ see TODO @@@ */
    short               m_Flags;
    oslWorkerFunction   m_WorkerFunction;
    void*               m_pData;
    pthread_mutex_t     m_Lock;
    pthread_cond_t      m_Cond;
} Thread_Impl;

#if !defined NO_PTHREAD_PRIORITY
struct osl_thread_priority_st
{
    int m_Highest;
    int m_Above_Normal;
    int m_Normal;
    int m_Below_Normal;
    int m_Lowest;
};
#define OSL_THREAD_PRIORITY_INITIALIZER { 127, 96, 64, 32, 0 }
#endif

}

#if !defined NO_PTHREAD_PRIORITY

namespace {

struct osl_thread_global_st
{
    pthread_once_t                    m_once;
    struct osl_thread_priority_st     m_priority;
};

}

static struct osl_thread_global_st g_thread =
{
    PTHREAD_ONCE_INIT,
    OSL_THREAD_PRIORITY_INITIALIZER
};

#endif // !defined NO_PTHREAD_PRIORITY

static Thread_Impl* osl_thread_construct_Impl();
static void         osl_thread_destruct_Impl (Thread_Impl ** ppImpl);

static void* osl_thread_start_Impl (void * pData);
static void  osl_thread_cleanup_Impl (Thread_Impl * pImpl);

static oslThread osl_thread_create_Impl (
    oslWorkerFunction pWorker, void * pThreadData, short nFlags);

/* @@@ see TODO @@@ */
static oslThreadIdentifier insertThreadId (pthread_t hThread);
static oslThreadIdentifier lookupThreadId (pthread_t hThread);
static void                removeThreadId (pthread_t hThread);

Thread_Impl* osl_thread_construct_Impl()
{
    Thread_Impl* pImpl = new Thread_Impl;
    memset (pImpl, 0, sizeof(Thread_Impl));

    pthread_mutex_init (&(pImpl->m_Lock), PTHREAD_MUTEXATTR_DEFAULT);
    pthread_cond_init  (&(pImpl->m_Cond), PTHREAD_CONDATTR_DEFAULT);
    return pImpl;
}

static void osl_thread_destruct_Impl (Thread_Impl ** ppImpl)
{
    assert(ppImpl);
    if (*ppImpl)
    {
        pthread_cond_destroy  (&((*ppImpl)->m_Cond));
        pthread_mutex_destroy (&((*ppImpl)->m_Lock));

        delete *ppImpl;
        (*ppImpl) = nullptr;
    }
}

static void osl_thread_cleanup_Impl (Thread_Impl * pImpl)
{
    pthread_t thread;
    bool attached;
    bool destroyed;

    pthread_mutex_lock (&(pImpl->m_Lock));

    thread = pImpl->m_hThread;
    attached = (pImpl->m_Flags & THREADIMPL_FLAGS_ATTACHED) != 0;
    destroyed = (pImpl->m_Flags & THREADIMPL_FLAGS_DESTROYED) != 0;
    pImpl->m_Flags &= ~(THREADIMPL_FLAGS_ACTIVE | THREADIMPL_FLAGS_ATTACHED);

    pthread_mutex_unlock (&(pImpl->m_Lock));

    /* release oslThreadIdentifier @@@ see TODO @@@ */
    removeThreadId (thread);

    if (attached)
    {
        pthread_detach (thread);
    }

    if (destroyed)
    {
        osl_thread_destruct_Impl (&pImpl);
    }
}

static void* osl_thread_start_Impl (void* pData)
{
    bool terminate;
    Thread_Impl* pImpl= static_cast<Thread_Impl*>(pData);

    assert(pImpl);

    pthread_mutex_lock (&(pImpl->m_Lock));

    /* request oslThreadIdentifier @@@ see TODO @@@ */
    pImpl->m_Ident = insertThreadId (pImpl->m_hThread);

    /* signal change from STARTUP to ACTIVE state */
    pImpl->m_Flags &= ~THREADIMPL_FLAGS_STARTUP;
    pImpl->m_Flags |=  THREADIMPL_FLAGS_ACTIVE;
    pthread_cond_signal (&(pImpl->m_Cond));

    /* Check if thread is started in SUSPENDED state */
    while (pImpl->m_Flags & THREADIMPL_FLAGS_SUSPENDED)
    {
        /* wait until SUSPENDED flag is cleared */
        pthread_cond_wait (&(pImpl->m_Cond), &(pImpl->m_Lock));
    }

    /* check for SUSPENDED to TERMINATE state change */
    terminate = ((pImpl->m_Flags & THREADIMPL_FLAGS_TERMINATE) > 0);

    pthread_mutex_unlock (&(pImpl->m_Lock));

    if (!terminate)
    {
#ifdef ANDROID
        JNIEnv* env = 0;
        int res = (*lo_get_javavm()).AttachCurrentThread(&env, NULL);
        __android_log_print(ANDROID_LOG_INFO, "LibreOffice", "New sal thread started and attached res=%d", res);
#endif
        /* call worker function */
        pImpl->m_WorkerFunction(pImpl->m_pData);

#ifdef ANDROID
        res = (*lo_get_javavm()).DetachCurrentThread();
        __android_log_print(ANDROID_LOG_INFO, "LibreOffice", "Detached finished sal thread res=%d", res);
#endif
    }

    osl_thread_cleanup_Impl (pImpl);
    return nullptr;
}

static oslThread osl_thread_create_Impl (
    oslWorkerFunction pWorker,
    void*             pThreadData,
    short             nFlags)
{
    Thread_Impl* pImpl;
#if defined OPENBSD || defined MACOSX || (defined LINUX && !ENABLE_RUNTIME_OPTIMIZATIONS)
    pthread_attr_t attr;
    size_t stacksize;
#endif
    int nRet=0;

    pImpl = osl_thread_construct_Impl();
    if (!pImpl)
        return nullptr; /* ENOMEM */

    pImpl->m_WorkerFunction = pWorker;
    pImpl->m_pData = pThreadData;
    pImpl->m_Flags = nFlags | THREADIMPL_FLAGS_STARTUP;

    pthread_mutex_lock (&(pImpl->m_Lock));

#if defined OPENBSD || defined MACOSX || (defined LINUX && !ENABLE_RUNTIME_OPTIMIZATIONS)
    if (pthread_attr_init(&attr) != 0)
        return nullptr;

#if defined OPENBSD
    stacksize = 262144;
#elif !ENABLE_RUNTIME_OPTIMIZATIONS
    stacksize = 12 * 1024 * 1024; // 8MB is not enough for ASAN on x86-64
#else
    stacksize = 1 * 1024 * 1024; // macOS default for non-main threads (512kB) is not enough...
#endif
    if (pthread_attr_setstacksize(&attr, stacksize) != 0) {
        pthread_attr_destroy(&attr);
        return nullptr;
    }
#endif

    if ((nRet = pthread_create (
        &(pImpl->m_hThread),
#if defined OPENBSD || defined MACOSX || (defined LINUX && !ENABLE_RUNTIME_OPTIMIZATIONS)
        &attr,
#else
        PTHREAD_ATTR_DEFAULT,
#endif
        osl_thread_start_Impl,
        static_cast<void*>(pImpl))) != 0)
    {
        SAL_WARN(
            "sal.osl",
            "pthread_create failed: " << UnixErrnoString(nRet));

        pthread_mutex_unlock (&(pImpl->m_Lock));
        osl_thread_destruct_Impl (&pImpl);

        return nullptr;
    }

#if defined OPENBSD || defined MACOSX || (defined LINUX && !ENABLE_RUNTIME_OPTIMIZATIONS)
    pthread_attr_destroy(&attr);
#endif

    /* wait for change from STARTUP to ACTIVE state */
    while (pImpl->m_Flags & THREADIMPL_FLAGS_STARTUP)
    {
        /* wait until STARTUP flag is cleared */
        pthread_cond_wait (&(pImpl->m_Cond), &(pImpl->m_Lock));
    }

    pthread_mutex_unlock (&(pImpl->m_Lock));

    return static_cast<oslThread>(pImpl);
}

oslThread osl_createThread (
    oslWorkerFunction pWorker,
    void *            pThreadData)
{
    return osl_thread_create_Impl (
        pWorker,
        pThreadData,
        THREADIMPL_FLAGS_ATTACHED);
}

oslThread osl_createSuspendedThread (
    oslWorkerFunction pWorker,
    void *            pThreadData)
{
    return osl_thread_create_Impl (
        pWorker,
        pThreadData,
        THREADIMPL_FLAGS_ATTACHED |
        THREADIMPL_FLAGS_SUSPENDED );
}

void SAL_CALL osl_destroyThread(oslThread Thread)
{
    if (Thread != nullptr) {
        Thread_Impl * impl = static_cast<Thread_Impl *>(Thread);
        bool active;
        pthread_mutex_lock(&impl->m_Lock);
        active = (impl->m_Flags & THREADIMPL_FLAGS_ACTIVE) != 0;
        impl->m_Flags |= THREADIMPL_FLAGS_DESTROYED;
        pthread_mutex_unlock(&impl->m_Lock);
        if (!active) {
            osl_thread_destruct_Impl(&impl);
        }
    }
}

void SAL_CALL osl_resumeThread(oslThread Thread)
{
    Thread_Impl* pImpl= static_cast<Thread_Impl*>(Thread);

    if (!pImpl)
    {
        SAL_WARN("sal.osl", "invalid osl_resumeThread(nullptr) call");
        return; /* EINVAL */
    }

    pthread_mutex_lock (&(pImpl->m_Lock));

    if (pImpl->m_Flags & THREADIMPL_FLAGS_SUSPENDED)
    {
        /* clear SUSPENDED flag */
        pImpl->m_Flags &= ~THREADIMPL_FLAGS_SUSPENDED;
        pthread_cond_signal (&(pImpl->m_Cond));
    }

    pthread_mutex_unlock (&(pImpl->m_Lock));
}

void SAL_CALL osl_suspendThread(oslThread Thread)
{
    Thread_Impl* pImpl= static_cast<Thread_Impl*>(Thread);

    if (!pImpl)
    {
        SAL_WARN("sal.osl", "invalid osl_suspendThread(nullptr) call");
        return; /* EINVAL */
    }

    pthread_mutex_lock (&(pImpl->m_Lock));

    pImpl->m_Flags |= THREADIMPL_FLAGS_SUSPENDED;

    if (pthread_equal (pthread_self(), pImpl->m_hThread))
    {
        /* self suspend */
        while (pImpl->m_Flags & THREADIMPL_FLAGS_SUSPENDED)
        {
            /* wait until SUSPENDED flag is cleared */
            pthread_cond_wait (&(pImpl->m_Cond), &(pImpl->m_Lock));
        }
    }

    pthread_mutex_unlock (&(pImpl->m_Lock));
}

sal_Bool SAL_CALL osl_isThreadRunning(const oslThread Thread)
{
    bool active;
    Thread_Impl* pImpl= static_cast<Thread_Impl*>(Thread);

    if (!pImpl)
        return false;

    pthread_mutex_lock (&(pImpl->m_Lock));
    active = ((pImpl->m_Flags & THREADIMPL_FLAGS_ACTIVE) > 0);
    pthread_mutex_unlock (&(pImpl->m_Lock));

    return active;
}

void SAL_CALL osl_joinWithThread(oslThread Thread)
{
    Thread_Impl* pImpl= static_cast<Thread_Impl*>(Thread);

    if (!pImpl)
        return;

    pthread_mutex_lock (&(pImpl->m_Lock));

    pthread_t const thread = pImpl->m_hThread;
    bool const attached = ((pImpl->m_Flags & THREADIMPL_FLAGS_ATTACHED) > 0);

    /* check this only if *this* thread is still attached - if it's not,
       then it could have terminated and another newly created thread could
       have recycled the same id as m_hThread! */
    if (attached && pthread_equal(pthread_self(), pImpl->m_hThread))
    {
        assert(false); /* Win32 implementation would deadlock here! */
        /* self join */
        pthread_mutex_unlock (&(pImpl->m_Lock));
        return; /* EDEADLK */
    }

    pImpl->m_Flags &= ~THREADIMPL_FLAGS_ATTACHED;

    pthread_mutex_unlock (&(pImpl->m_Lock));

    if (attached)
    {
        pthread_join (thread, nullptr);
    }
}

void SAL_CALL osl_terminateThread(oslThread Thread)
{
    Thread_Impl* pImpl= static_cast<Thread_Impl*>(Thread);

    if (!pImpl)
    {
        SAL_WARN("sal.osl", "invalid osl_terminateThread(nullptr) call");
        return; /* EINVAL */
    }

    pthread_mutex_lock (&(pImpl->m_Lock));

    if (pImpl->m_Flags & THREADIMPL_FLAGS_SUSPENDED)
    {
        /* clear SUSPENDED flag */
        pImpl->m_Flags &= ~THREADIMPL_FLAGS_SUSPENDED;
        pthread_cond_signal (&(pImpl->m_Cond));
    }

    pImpl->m_Flags |= THREADIMPL_FLAGS_TERMINATE;

    pthread_mutex_unlock (&(pImpl->m_Lock));
}

sal_Bool SAL_CALL osl_scheduleThread(oslThread Thread)
{
    bool terminate;
    Thread_Impl* pImpl= static_cast<Thread_Impl*>(Thread);

    if (!pImpl)
    {
        SAL_WARN("sal.osl", "invalid osl_scheduleThread(nullptr) call");
        return false; /* EINVAL */
    }

    if (!(pthread_equal (pthread_self(), pImpl->m_hThread)))
    {
        SAL_WARN("sal.osl", "invalid osl_scheduleThread(non-self) call");
        return false; /* EINVAL */
    }

    pthread_mutex_lock (&(pImpl->m_Lock));

    while (pImpl->m_Flags & THREADIMPL_FLAGS_SUSPENDED)
    {
        /* wait until SUSPENDED flag is cleared */
        pthread_cond_wait (&(pImpl->m_Cond), &(pImpl->m_Lock));
    }

    terminate = ((pImpl->m_Flags & THREADIMPL_FLAGS_TERMINATE) > 0);

    pthread_mutex_unlock(&(pImpl->m_Lock));

    return !terminate;
}

void SAL_CALL osl_waitThread(const TimeValue* pDelay)
{
    if (pDelay)
    {
        struct timespec delay;

        SET_TIMESPEC(delay, pDelay->Seconds, pDelay->Nanosec);

        SLEEP_TIMESPEC(delay);
    }
}

/** Yields thread

    @attention Note that POSIX scheduling @em really requires threads to call this
    function, since a thread only reschedules to other thread, when
    it blocks (sleep, blocking I/O) OR calls sched_yield().
*/
void SAL_CALL osl_yieldThread()
{
    sched_yield();
}

void SAL_CALL osl_setThreadName(char const * name)
{
    assert( name );
#if defined LINUX && ! defined __FreeBSD_kernel__
    const int LINUX_THREAD_NAME_MAXLEN = 15;
    if ( strlen( name ) > LINUX_THREAD_NAME_MAXLEN )
        SAL_INFO( "sal.osl", "osl_setThreadName truncated thread name to "
                  << LINUX_THREAD_NAME_MAXLEN << " chars from name '"
                  << name << "'" );
    char shortname[ LINUX_THREAD_NAME_MAXLEN + 1 ];
    shortname[ LINUX_THREAD_NAME_MAXLEN ] = '\0';
    strncpy( shortname, name, LINUX_THREAD_NAME_MAXLEN );
    int err = pthread_setname_np( pthread_self(), shortname );
    if ( 0 != err )
        SAL_WARN("sal.osl", "pthread_setname_np failed with errno " << err);
#elif defined __FreeBSD__
    pthread_setname_np( pthread_self(), name );
#elif defined MACOSX || defined IOS
    pthread_setname_np( name );
#else
    (void) name;
#endif
}

/* osl_getThreadIdentifier @@@ see TODO @@@ */

namespace {

struct HashEntry
{
    pthread_t            Handle;
    oslThreadIdentifier  Ident;
    HashEntry *          Next;
};

}

static HashEntry* HashTable[31];
const int HashSize = SAL_N_ELEMENTS(HashTable);

static std::mutex HashLock;

#if ! ((defined LINUX && !defined __FreeBSD_kernel__) || defined MACOSX || defined IOS)
static oslThreadIdentifier LastIdent = 0;
#endif

namespace {

std::size_t HASHID(pthread_t x)
{ return std::hash<pthread_t>()(x) % HashSize; }

}

static oslThreadIdentifier lookupThreadId (pthread_t hThread)
{
    HashEntry *pEntry;

    std::unique_lock aGuard(HashLock);

    pEntry = HashTable[HASHID(hThread)];
    while (pEntry != nullptr)
    {
        if (pthread_equal(pEntry->Handle, hThread))
        {
            return pEntry->Ident;
        }
        pEntry = pEntry->Next;
    }

    return 0;
}

static oslThreadIdentifier insertThreadId (pthread_t hThread)
{
    HashEntry *pEntry, *pInsert = nullptr;

    std::unique_lock aGuard(HashLock);

    pEntry = HashTable[HASHID(hThread)];

    while (pEntry != nullptr)
    {
        if (pthread_equal(pEntry->Handle, hThread))
            break;

        pInsert = pEntry;
        pEntry = pEntry->Next;
    }

    if (pEntry == nullptr)
    {
        pEntry = static_cast<HashEntry*>(calloc(1, sizeof(HashEntry)));

        pEntry->Handle = hThread;

#if defined LINUX && ! defined __FreeBSD_kernel__
#if defined __GLIBC__ && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 30))
        // gettid returns a pid_t, which POSIX defines to be a signed integer type; assume that all
        // valid pid_t values on Linux are positive (zero is filtered out in the generic code
        // below):
        pid_t const tid = gettid();
        assert(tid >= 0);
#else
        long const tid = syscall(SYS_gettid);
        if (tid < 0 || o3tl::make_unsigned(tid) > std::numeric_limits<sal_uInt32>::max()) {
            std::abort();
        }
#endif
        pEntry->Ident = tid;
#elif defined MACOSX || defined IOS
        // currently the value of pthread_threadid_np is the same then
        // syscall(SYS_thread_selfid), which returns an int as the TID.
        // may change, as the syscall interface was deprecated.
        uint64_t mac_tid;
        pthread_threadid_np(nullptr, &mac_tid);
        if (mac_tid > SAL_MAX_UINT32)
            std::abort();
        pEntry->Ident = mac_tid;
#else
        ++LastIdent;
        if (0 == LastIdent)
            LastIdent = 1;
        pEntry->Ident = LastIdent;
#endif
        if (0 == pEntry->Ident)
            std::abort();

        if (pInsert)
            pInsert->Next = pEntry;
        else
            HashTable[HASHID(hThread)] = pEntry;
    }

    return pEntry->Ident;
}

static void removeThreadId (pthread_t hThread)
{
    HashEntry *pEntry, *pRemove = nullptr;

    std::unique_lock aGuard(HashLock);

    pEntry = HashTable[HASHID(hThread)];
    while (pEntry != nullptr)
    {
        if (pthread_equal(pEntry->Handle, hThread))
            break;

        pRemove = pEntry;
        pEntry = pEntry->Next;
    }

    if (pEntry != nullptr)
    {
        if (pRemove)
            pRemove->Next = pEntry->Next;
        else
            HashTable[HASHID(hThread)] = pEntry->Next;

        free(pEntry);
    }
}

oslThreadIdentifier SAL_CALL osl_getThreadIdentifier(oslThread Thread)
{
    Thread_Impl* pImpl= static_cast<Thread_Impl*>(Thread);
    oslThreadIdentifier Ident;

    if (pImpl)
        Ident = pImpl->m_Ident;
    else
    {
        /* current thread */
        pthread_t current = pthread_self();

        Ident = lookupThreadId (current);
        if (Ident == 0)
            /* @@@ see TODO: alien pthread_self() @@@ */
            Ident = insertThreadId (current);
    }

    return Ident;
}

#ifndef NO_PTHREAD_PRIORITY
/*****************************************************************************
    @@@ see TODO @@@
    osl_thread_priority_init_Impl

    set the base-priority of the main-thread to
    oslThreadPriorityNormal (64) since 0 (lowest) is
    the system default. This behaviour collides with
    our enum-priority definition (highest..normal..lowest).
    A  normaluser will expect the main-thread of an app.
    to have the "normal" priority.

*****************************************************************************/
static void osl_thread_priority_init_Impl()
{
    struct sched_param param;
    int policy=0;
    int nRet=0;

/* @@@ see TODO: calling thread may not be main thread @@@ */

    if ((nRet = pthread_getschedparam(pthread_self(), &policy, &param)) != 0)
    {
        SAL_WARN(
            "sal.osl",
            "pthread_getschedparam failed: " << UnixErrnoString(nRet));
        return;
    }

#if defined (__sun)
    if ( policy >= _SCHED_NEXT)
    {
        /* mfe: pthread_getschedparam on Solaris has a possible Bug */
        /*      one gets 959917873 as the policy                    */
        /*      so set the policy to a default one                  */
        policy=SCHED_OTHER;
    }
#endif /* __sun */

    if ((nRet = sched_get_priority_min(policy) ) != -1)
    {
        SAL_INFO(
            "sal.osl", "Min Prioriy for policy " << policy << " == " << nRet);
        g_thread.m_priority.m_Lowest=nRet;
    }
    else
    {
        int e = errno;
        SAL_WARN(
            "sal.osl",
            "sched_get_priority_min failed: " << UnixErrnoString(e));
    }

    if ((nRet = sched_get_priority_max(policy) ) != -1)
    {
        SAL_INFO(
            "sal.osl", "Max Prioriy for policy " << policy << " == " << nRet);
        g_thread.m_priority.m_Highest=nRet;
    }
    else
    {
        int e = errno;
        SAL_WARN(
            "sal.osl",
            "sched_get_priority_max failed: " << UnixErrnoString(e));
    }

    g_thread.m_priority.m_Normal =
        (g_thread.m_priority.m_Lowest + g_thread.m_priority.m_Highest) / 2;
    g_thread.m_priority.m_Below_Normal =
        (g_thread.m_priority.m_Lowest + g_thread.m_priority.m_Normal)  / 2;
    g_thread.m_priority.m_Above_Normal =
        (g_thread.m_priority.m_Normal + g_thread.m_priority.m_Highest) / 2;

/* @@@ set prio of calling (not main) thread (?) @@@ */

    param.sched_priority= g_thread.m_priority.m_Normal;

    if ((nRet = pthread_setschedparam(pthread_self(), policy, &param)) != 0)
    {
        SAL_WARN(
            "sal.osl",
            "pthread_setschedparam failed: " << UnixErrnoString(nRet));
        SAL_INFO(
            "sal.osl",
            "Thread ID " << pthread_self() << ", Policy " << policy
                << ", Priority " << param.sched_priority);
    }

}
#endif /* NO_PTHREAD_PRIORITY */

/**
    Impl-Notes: contrary to solaris-docu, which claims
    valid priority-levels from 0 .. INT_MAX, only the
    range 0..127 is accepted. (0 lowest, 127 highest)
*/
void SAL_CALL osl_setThreadPriority (
    oslThread         Thread,
    oslThreadPriority Priority)
{
#ifndef NO_PTHREAD_PRIORITY

    struct sched_param Param;
    int policy;
    int nRet;

#endif /* NO_PTHREAD_PRIORITY */

    Thread_Impl* pImpl= static_cast<Thread_Impl*>(Thread);

    if (!pImpl)
    {
        SAL_WARN("sal.osl", "invalid osl_setThreadPriority(nullptr, ...) call");
        return; /* EINVAL */
    }

#ifdef NO_PTHREAD_PRIORITY
    (void) Priority; /* unused */
#else /* NO_PTHREAD_PRIORITY */

    if (pthread_getschedparam(pImpl->m_hThread, &policy, &Param) != 0)
        return; /* ESRCH */

#if defined (__sun)
    if ( policy >= _SCHED_NEXT)
    {
        /* mfe: pthread_getschedparam on Solaris has a possible Bug */
        /*      one gets 959917873 as the policy                   */
        /*      so set the policy to a default one                 */
        policy=SCHED_OTHER;
    }
#endif /* __sun */

    pthread_once (&(g_thread.m_once), osl_thread_priority_init_Impl);

    switch(Priority)
    {
        case osl_Thread_PriorityHighest:
            Param.sched_priority= g_thread.m_priority.m_Highest;
            break;

        case osl_Thread_PriorityAboveNormal:
            Param.sched_priority= g_thread.m_priority.m_Above_Normal;
            break;

        case osl_Thread_PriorityNormal:
            Param.sched_priority= g_thread.m_priority.m_Normal;
            break;

        case osl_Thread_PriorityBelowNormal:
            Param.sched_priority= g_thread.m_priority.m_Below_Normal;
            break;

        case osl_Thread_PriorityLowest:
            Param.sched_priority= g_thread.m_priority.m_Lowest;
            break;

        case osl_Thread_PriorityUnknown:
            SAL_WARN(
                "sal.osl",
                "invalid osl_setThreadPriority(..., osl_Thread_PriorityUnknown)"
                    " call");
            return;

        default:
            SAL_WARN(
                "sal.osl",
                "invalid osl_setThreadPriority(..., " << Priority << ") call");
            return;
    }

    if ((nRet = pthread_setschedparam(pImpl->m_hThread, policy, &Param)) != 0)
    {
        SAL_WARN(
            "sal.osl",
            "pthread_setschedparam failed: " << UnixErrnoString(nRet));
    }

#endif /* NO_PTHREAD_PRIORITY */
}

oslThreadPriority SAL_CALL osl_getThreadPriority(const oslThread Thread)
{
#ifndef NO_PTHREAD_PRIORITY

    struct sched_param Param;
    int Policy;

#endif /* NO_PTHREAD_PRIORITY */

    oslThreadPriority Priority = osl_Thread_PriorityNormal;
    Thread_Impl* pImpl= static_cast<Thread_Impl*>(Thread);

    if (!pImpl)
    {
        SAL_WARN("sal.osl", "invalid osl_getThreadPriority(nullptr) call");
        return osl_Thread_PriorityUnknown; /* EINVAL */
    }

#ifndef NO_PTHREAD_PRIORITY

    if (pthread_getschedparam(pImpl->m_hThread, &Policy, &Param) != 0)
        return osl_Thread_PriorityUnknown; /* ESRCH */

    pthread_once (&(g_thread.m_once), osl_thread_priority_init_Impl);

    /* map pthread priority to enum */
    if (Param.sched_priority==g_thread.m_priority.m_Highest)
    {
        /* 127 - highest */
        Priority= osl_Thread_PriorityHighest;
    }
    else if (Param.sched_priority > g_thread.m_priority.m_Normal)
    {
        /* 65..126 - above normal */
        Priority= osl_Thread_PriorityAboveNormal;
    }
    else if (Param.sched_priority == g_thread.m_priority.m_Normal)
    {
        /* normal */
        Priority= osl_Thread_PriorityNormal;
    }
    else if (Param.sched_priority > g_thread.m_priority.m_Lowest)
    {
        /* 63..1 -below normal */
        Priority= osl_Thread_PriorityBelowNormal;
    }
    else if (Param.sched_priority == g_thread.m_priority.m_Lowest)
    {
        /* 0 - lowest */
        Priority= osl_Thread_PriorityLowest;
    }
    else
    {
        /* unknown */
        Priority= osl_Thread_PriorityUnknown;
    }

#endif /* NO_PTHREAD_PRIORITY */

    return Priority;
}

namespace {

struct wrapper_pthread_key
{
    pthread_key_t m_key;
    oslThreadKeyCallbackFunction pfnCallback;
};

}

oslThreadKey SAL_CALL osl_createThreadKey( oslThreadKeyCallbackFunction pCallback )
{
    wrapper_pthread_key *pKey = static_cast<wrapper_pthread_key*>(malloc(sizeof(wrapper_pthread_key)));

    if (pKey)
    {
        pKey->pfnCallback = pCallback;

        if (pthread_key_create(&(pKey->m_key), pKey->pfnCallback) != 0)
        {
            free(pKey);
            pKey = nullptr;
        }
    }

    return static_cast<oslThreadKey>(pKey);
}

void SAL_CALL osl_destroyThreadKey(oslThreadKey Key)
{
    wrapper_pthread_key *pKey = static_cast<wrapper_pthread_key*>(Key);
    if (pKey)
    {
        pthread_key_delete(pKey->m_key);
        free(pKey);
    }
}

void* SAL_CALL osl_getThreadKeyData(oslThreadKey Key)
{
    wrapper_pthread_key *pKey = static_cast<wrapper_pthread_key*>(Key);
    return pKey ? pthread_getspecific(pKey->m_key) : nullptr;
}

sal_Bool SAL_CALL osl_setThreadKeyData(oslThreadKey Key, void *pData)
{
    bool bRet;
    void *pOldData = nullptr;
    wrapper_pthread_key *pKey = static_cast<wrapper_pthread_key*>(Key);
    if (!pKey)
        return false;

    if (pKey->pfnCallback)
        pOldData = pthread_getspecific(pKey->m_key);

    bRet = (pthread_setspecific(pKey->m_key, pData) == 0);

    if (bRet && pKey->pfnCallback && pOldData)
        pKey->pfnCallback(pOldData);

    return bRet;
}

rtl_TextEncoding getThreadTextEncodingForInitialization()
{
    /* determine default text encoding */
    rtl_TextEncoding defaultEncoding = osl_getTextEncodingFromLocale(nullptr);
    // Tools string functions call abort() on an unknown encoding so ASCII is a
    // meaningful fallback:
    if ( RTL_TEXTENCODING_DONTKNOW == defaultEncoding )
    {
        SAL_WARN("sal.osl", "RTL_TEXTENCODING_DONTKNOW -> _ASCII_US");
        defaultEncoding = RTL_TEXTENCODING_ASCII_US;
    }

    return defaultEncoding;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */