// $Id: linux_threads.c,v 1.21 2016/06/07 21:16:08 lpoulsen Exp $
// Copyright (C) 2001 Wi_LAN Technologies, Inc
//
// $Log: linux_threads.c,v $
// Revision 1.21  2016/06/07 21:16:08  lpoulsen
// Resolve problem in Linux ECON causing a hard hanging process when you
//     do a multi-radio connect/download sequence. The resolution was to
//     use a local mutex instead of a global mutex to bracket the
//     pthread_cond_timedwait() in WaitForSingleObject()
// Also, use DBFLG_ECON_PKTS to turn on debugging output of ECON packets
//     instead of debug_level
//
// Revision 1.20  2011/05/18 18:51:23  lpoulsen
// Runs on Fedora 14
//
// Revision 1.19  2011/03/02 18:56:26  lpoulsen
// Ctrl-C works on Linux
//
// Revision 1.18  2011/03/02 18:19:35  lpoulsen
// Allow Ctrl-C to close a session
//
// Revision 1.17  2008/08/05 23:58:37  lpoulsen
// User interface updates.
//
// Revision 1.16  2008/07/14 23:28:38  lpoulsen
// Adapt to new interface for threads.
//
// Revision 1.15  2008/07/12 01:32:41  lpoulsen
// Fix errors
//
// Revision 1.14  2008/07/11 20:14:10  lpoulsen
// Resolve many compiler warnings on Linux
//
// Revision 1.13  2005/06/24 18:17:44  lpoulsen
// mprove portability (for OpenBSD)
//
// Revision 1.12  2005/06/23 00:13:30  lpoulsen
// Reduce dependency on pthread.h implementation specifics.
//
// Revision 1.11  2004/07/14 00:34:46  lpoulsen
// Updated for latest Linux version and merged with 02.07
//
// Revision 1.10  2003/08/25 20:22:36  lpoulsen
// Minor clean-up.
//
// Revision 1.9  2003/08/25 20:16:46  lpoulsen
// Clean up exit for FileThread ...
//
// Revision 1.8  2002/05/21 17:53:50  lpoulsen
// Fix many compiler warnings.
//
// Revision 1.7  2001/11/15 23:08:16  mourlouk
// Made a small change at linux_threads.c
//
// Revision 1.6  2001/11/13 01:23:33  lpoulsen
// Remove compiler warnings
//
// Revision 1.5  2001/11/12 22:52:32  mourlouk
// making compiler happy (I)
//
// Revision 1.4  2001/11/12 21:55:34  mourlouk
// Included errno.h at linux_threads.c
//
// Revision 1.3  2001/11/06 23:24:23  mourlouk
// ....
//
// Revision 1.2  2001/10/30 01:07:45  lpoulsen
// Add "module ID" statement, audit trail generator and copyright line
// Remove "win_or_linux.h" and use a -D in Linux makefile instead.
//
//

#include <stdio.h>
#include <sys/time.h>           /* struct timeval definition           */
#include <unistd.h>             /* declaration of gettimeofday()       */
#include <errno.h>
#include "afartype.h"
#include "linux_threads.h"
#include "win_emul.h"
#include "epacket.h"
#include <signal.h>
#include "ucon.h"

#define THREAD_FAILURE -19

typedef void (*sig_t)(int);
void        econ_kill_handler(int);

extern void initCIOSettings();
extern void restoreCIOSettings();
extern void terminate(int exitCode, int errorCode);

extern void *SendThread(void *radioID);
extern void *ReadThread(void *radioID);
extern void *ReceiveThread(void *radioID);
extern void *FileThread(void *radioID);

pthread_mutex_t global_mutex;

void
self_init_thread()
{
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
}

void
self_terminate_thread()
{
    pthread_exit(0);
}

// read thread requires some special treatment in the Windows platform.
void
readThreadInit()
{
    initCIOSettings();
}

void
readThreadTerminate()
{
    restoreCIOSettings();
}

void
start_all_threads()
{
    pthread_mutex_init(&global_mutex, NULL);

    signal(SIGINT, econ_kill_handler);

    send_thr_id =
        pthread_create(&send_thread, NULL, SendThread,
                       (void *) &active_session);
    read_thr_id =
        pthread_create(&read_thread, NULL, ReadThread,
                       (void *) &active_session);
    receive_thr_id =
        pthread_create(&receive_thread, NULL, ReceiveThread,
                       (void *) &active_session);
    file_thr_id =
        pthread_create(&file_thread, NULL, FileThread,
                       (void *) &active_session);

    if ((send_thr_id) || (read_thr_id) || (receive_thr_id) || (file_thr_id))
        terminate(-1, THREAD_FAILURE);
}

void
wait_for_all_threads_to_terminate()
{
    if (debug_flags & DBFLG_THREADS) printf("[all threads started - econ_phase = %d]\n", econ_phase);
    while ((econ_phase != ECON_CLOSING_SESSION) && (econ_phase != ECON_CLOSE_THREADS)) {
        usleep(100000); // sleep for 100 ms
    }
    if (debug_flags & DBFLG_THREADS) printf("[joining SendThread]\n");
    if (pthread_join(send_thread, NULL))
        terminate(-1, THREAD_FAILURE);
    if (debug_flags & DBFLG_THREADS) printf("[joining ReadThread]\n");
    if (pthread_join(read_thread, NULL))
        terminate(-1, THREAD_FAILURE);
    if (debug_flags & DBFLG_THREADS) printf("[joining ReceiveThread]\n");
    if (pthread_join(receive_thread, NULL))
        terminate(-1, THREAD_FAILURE);
    if (debug_flags & DBFLG_THREADS) printf("[joining FileThread]\n");
    if (pthread_join(file_thread, NULL))
        terminate(-1, THREAD_FAILURE);
    if (debug_flags & DBFLG_THREADS) printf("[All threads joined]\n");
    signal(SIGINT, SIG_DFL);
}

void
terminate_all_threads(int read_thread_is_last)
{
    extern pthread_cond_t *File_ACK;
    extern pthread_cond_t *available_data;
    extern pthread_cond_t *server_flags_updated;
    extern boolean exiting;

    exiting = true;

    restoreCIOSettings();

    if (debug_flags & DBFLG_THREADS)
        printf("[terminate_all_threads]\n");

    pthread_cancel(send_thread);
    pthread_cancel(file_thread);

    if (read_thread_is_last)
    {
        pthread_cancel(receive_thread);
        pthread_cancel(read_thread);
    }
    else
    {
        pthread_cancel(read_thread);
        pthread_cancel(receive_thread);
    }
    SetEvent(File_ACK);
    SetEvent(available_data);
    SetEvent(server_flags_updated);
    econ_phase = ECON_CLOSE_THREADS;
}

// Windows' thread emulation

int
EnterCriticalSection(pthread_mutex_t * my_mutex)
{
    int rc = pthread_mutex_lock(my_mutex);
    if (rc)
    {
        perror("pthread_mutex_lock");
        pthread_exit(NULL);
    }
    return rc;
}

int
LeaveCriticalSection(pthread_mutex_t * my_mutex)
{
    int rc = pthread_mutex_unlock(my_mutex);
    return rc;
}

int
ResetEvent(pthread_cond_t * my_condition)
{
    pthread_cond_broadcast(my_condition);
    return 0;                   // everything OK
}

int
SetEvent(pthread_cond_t * my_condition)
{
    pthread_cond_broadcast(my_condition);
    return 0;                   // everything OK
}

int
WaitForSingleObject(pthread_cond_t * my_condition, int time_out)
{
    static struct timeval now;  // time when we started waiting
    static struct timespec timeout; // timeout value for the wait function
    static long tmpMicros;
    int res;
    pthread_mutex_t local_mutex;

    pthread_mutex_init(&local_mutex, NULL);

    if (time_out == INFINITE)
    {
        pthread_mutex_lock(&local_mutex);
        res = pthread_cond_wait(my_condition, &global_mutex);
        pthread_mutex_unlock(&local_mutex);

        switch (res)
        {
        case 0:                // everything OK - no timeout
            return WAIT_OBJECT_0;
        case ETIMEDOUT:
            return WAIT_TIMEOUT;
        default:
            return WAIT_FAILED;
        }
    }
    else
    {
        gettimeofday(&now, NULL);
        timeout.tv_sec = now.tv_sec + (time_out / 1000);
        tmpMicros = now.tv_usec + (time_out % 1000) * 1000;
        if (tmpMicros > 1000000)
        {
            timeout.tv_sec++;
            timeout.tv_nsec = (tmpMicros - 1000000) * 1000;
        }
        else
            timeout.tv_nsec = tmpMicros * 1000;

        pthread_mutex_lock(&local_mutex);
        // printf(".... timedwait %ld\n",my_condition);
        res = pthread_cond_timedwait(my_condition, &local_mutex, &timeout);
        pthread_mutex_unlock(&local_mutex);

        switch (res)
        {
        case 0:                // everything OK - no timeout
            return WAIT_OBJECT_0;
        case ETIMEDOUT:
            return WAIT_TIMEOUT;
        default:
            return WAIT_FAILED;
        }
    }
}

void
econ_kill_handler(int arg)
{
    if (session_result == ES_CTRL_C)
        return;
    if (arg == SIGINT)
        printf("[Ctrl-C]\n");
    else
        printf("[SIG %d]\n", arg);
    session_result = ES_CTRL_C;
    econ_phase = ECON_CLOSE_THREADS;
}
