/* ========================================================================== */
/*! \file
 * \brief Thread safe replacement functions
 *
 * Copyright (c) 2012-2015 by the developers. See the LICENSE file for details.
 * If nothing else is specified, functions return zero to indicate success
 * and a negative value to indicate an error.
 */


/* ========================================================================== */
/* Include headers */

#include "posix.h"  /* Include this first because of feature test macros */

#include "main.h"


/* ========================================================================== */
/*! \addtogroup MAIN
 *
 * The POSIX \c getenv() function is not required to be thread safe. Therefore
 * we must provide our own. It was considered to clone the getenv_r() function
 * from BSD, but this function have no buffer management and is inconvenient to
 * use. The function \c ts_getenv() dynamically allocate or resize the thread
 * local buffer internally.
 *
 * The functions \c ts_gmtime() and \c ts_localtime() behave like the POSIX
 * functions \c gmtime_r() and \c localtime_r() but are implemented on top of
 * the corresponding C89 functions.
 */
/* @{ */


/* ========================================================================== */
/* Variables */

static posix_pthread_mutex_t  getenv_mutex = POSIX_PTHREAD_MUTEX_INITIALIZER;
static posix_pthread_mutex_t  gmtime_mutex = POSIX_PTHREAD_MUTEX_INITIALIZER;
static posix_pthread_mutex_t  localtime_mutex = POSIX_PTHREAD_MUTEX_INITIALIZER;


/* ========================================================================== */
/*! \brief Thread safe replacement for \c getenv()
 *
 * \param[in]     name  Name of the requested environment variable
 * \param[in,out] buf   Pointer to pointer to buffer for result
 *
 * Dereferenced \e buf must be either \c NULL or an existing dynamically
 * allocated buffer that can be resized with \c realloc() if required.
 *
 * The buffer pointed to by \e buf is created or resized so that it can hold the
 * result and the value of the environment variable \e name is copied to it.
 *
 * The caller is responsible to free the memory allocated for the buffer on
 * success.
 *
 * \return
 * - 0 on success
 * - Negative value on error
 */

int  ts_getenv(const char*  name, const char**  buf)
{
   int  res = -1;
   int  rv;
   char*  content;
   char*  tmp;
   size_t  len;

   if(NULL != buf)
   {
      /* Lock mutex */
      rv = posix_pthread_mutex_lock(&getenv_mutex);
      if(rv)  { print_error("MAIN: Locking mutex failed"); }
      else
      {
         /* Lock UI mutex */
         rv = ts_lock_ui();
         if(rv)  { print_error("MAIN: Locking UI mutex failed"); }
         else
         {
            /* Get variable from environment */
            content = posix_getenv(name);
            if(NULL != content)
            {
               /* Copy content to target buffer */
               len = strlen(content);
               tmp = (char*) posix_realloc((void*) *buf, ++len);
               if(NULL != tmp)
               {
                  strcpy(tmp, content);
                  *buf = tmp;
                  res = 0;
               }
            }
            /* Unlock UI mutex */
            rv = ts_unlock_ui();
            if(rv)  { print_error("MAIN: Unlocking UI mutex failed"); }
         }
         /* Unlock mutex after content was copied to thread local buffer */
         rv = posix_pthread_mutex_unlock(&getenv_mutex);
         if(rv)  { print_error("MAIN: Unlocking mutex failed"); }
      }
      /* Check for error */
      if(res)
      {
         posix_free((void*) *buf);
         *buf = NULL;
      }
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Thread safe replacement for \c gmtime()
 *
 * \param[in]  timer   Time in seconds since the Epoch
 * \param[out] result  Broken down time expressed in UTC
 *
 * \return
 * - 0 on success
 * - Negative value on error
 */

struct tm*  ts_gmtime(const time_t*  timer, struct tm*  result)
{
   struct tm*  res = result;
   int  rv;
   struct tm*  t;

   /* Lock mutex */
   rv = posix_pthread_mutex_lock(&gmtime_mutex);
   if(rv)  { print_error("MAIN: Locking mutex failed");  res = NULL; }
   else
   {
      /* Lock UI mutex */
      rv = ts_lock_ui();
      if(rv)  { print_error("MAIN: Locking UI mutex failed"); }
      else
      {
         t = gmtime(timer);
         if(NULL == t)  { res = NULL; }
         else  { memcpy((void*) result, (void*) t, sizeof(struct tm)); }
         /* Unlock UI mutex */
         rv = ts_unlock_ui();
         if(rv)  { print_error("MAIN: Unlocking UI mutex failed"); }
      }
      /* Unlock mutex after content was copied to thread local buffer */
      rv = posix_pthread_mutex_unlock(&gmtime_mutex);
      if(rv)  { print_error("MAIN: Unlocking mutex failed");  res = NULL; }
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Thread safe replacement for \c localtime()
 *
 * \param[in]  timer   Time in seconds since the Epoch
 * \param[out] result  Broken down time expressed in UTC
 *
 * \return
 * - 0 on success
 * - Negative value on error
 */

struct tm*  ts_localtime(const time_t*  timer, struct tm*  result)
{
   struct tm*  res = result;
   int  rv;
   struct tm*  t;

   /* Lock mutex */
   rv = posix_pthread_mutex_lock(&localtime_mutex);
   if(rv)  { print_error("MAIN: Locking mutex failed");  res = NULL; }
   else
   {
      /* Lock UI mutex */
      rv = ts_lock_ui();
      if(rv)  { print_error("MAIN: Locking UI mutex failed"); }
      else
      {
         t = localtime(timer);
         if(NULL == t)  { res = NULL; }
         else  { memcpy((void*) result, (void*) t, sizeof(struct tm)); }
         /* Unlock UI mutex */
         rv = ts_unlock_ui();
         if(rv)  { print_error("MAIN: Unlocking UI mutex failed"); }
      }
      /* Unlock mutex after content was copied to thread local buffer */
      rv = posix_pthread_mutex_unlock(&localtime_mutex);
      if(rv)  { print_error("MAIN: Unlocking mutex failed");  res = NULL; }
   }

   return(res);
}


/* @} */

/* EOF */
