/* Copyright (c) 2014, 2021, Oracle and/or its affiliates.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License, version 2.0,
   as published by the Free Software Foundation.

   This program is also distributed with certain software (including
   but not limited to OpenSSL) that is licensed under separate terms,
   as designated in a particular file or component or in included license
   documentation.  The authors of MySQL hereby grant you an additional
   permission to link the program and your derivative works with the
   separately licensed software that they have included with MySQL.

   Without limiting anything contained in the foregoing, this file,
   which is part of C Driver for MySQL (Connector/C), is also subject to the
   Universal FOSS Exception, version 1.0, a copy of which can be found at
   http://oss.oracle.com/licenses/universal-foss-exception.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License, version 2.0, for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA */


#include "my_global.h"
#include "my_thread.h"      /* my_thread_init, my_thread_end */
#include "my_sys.h"         /* my_message_local */
#include "my_timer.h"       /* my_timer_t */

#include <string.h>         /* memset */
#include <signal.h>

#if defined(HAVE_SIGEV_THREAD_ID)
#include <sys/syscall.h>    /* SYS_gettid */

#ifndef sigev_notify_thread_id
#define sigev_notify_thread_id   _sigev_un._tid
#endif

#define MY_TIMER_EVENT_SIGNO  (SIGRTMIN)
#define MY_TIMER_KILL_SIGNO   (SIGRTMIN+1)

/* Timer thread ID (TID). */
static pid_t timer_notify_thread_id;

#elif defined(HAVE_SIGEV_PORT)
#include <port.h>

int port_id= -1;

#endif

/* Timer thread object. */
static my_thread_handle timer_notify_thread;

#if defined(HAVE_SIGEV_THREAD_ID)
/**
  Timer expiration notification thread.

  @param  arg   Barrier object.
*/

static void *
timer_notify_thread_func(void *arg)
{
  sigset_t set;
  siginfo_t info;
  my_timer_t *timer;
  pthread_barrier_t *barrier= arg;

  my_thread_init();

  sigemptyset(&set);
  sigaddset(&set, MY_TIMER_EVENT_SIGNO);
  sigaddset(&set, MY_TIMER_KILL_SIGNO);

  /* Get the thread ID of the current thread. */
  timer_notify_thread_id= (pid_t) syscall(SYS_gettid);

  /* Wake up parent thread, timer_notify_thread_id is available. */
  pthread_barrier_wait(barrier);

  while (1)
  {
    if (sigwaitinfo(&set, &info) < 0)
      continue;

    if (info.si_signo == MY_TIMER_EVENT_SIGNO)
    {
      timer= (my_timer_t*)info.si_value.sival_ptr;
      timer->notify_function(timer);
    }
    else if (info.si_signo == MY_TIMER_KILL_SIGNO)
      break;
  }

  my_thread_end();

  return NULL;
}


/**
  Create a helper thread to dispatch timer expiration notifications.

  @return On success, 0. On error, -1 is returned.
*/

static int
start_helper_thread(void)
{
  pthread_barrier_t barrier;

  if (pthread_barrier_init(&barrier, NULL, 2))
  {
    my_message_local(ERROR_LEVEL,
                     "Failed to initialize pthread barrier. errno=%d", errno);
    return -1;
  }

  if (mysql_thread_create(key_thread_timer_notifier, &timer_notify_thread,
                          NULL, timer_notify_thread_func, &barrier))
  {
    my_message_local(ERROR_LEVEL,
                     "Failed to create timer notify thread (errno= %d).",
                     errno);
    pthread_barrier_destroy(&barrier);
    return -1;
  }

  pthread_barrier_wait(&barrier);
  pthread_barrier_destroy(&barrier);

  return 0;
}


/**
  Initialize internal components.

  @return On success, 0.
          On error, -1 is returned, and errno is set to indicate the error.
*/

int
my_timer_initialize(void)
{
  int rc;
  sigset_t set, old_set;

  if (sigfillset(&set))
  {
    my_message_local(ERROR_LEVEL,
                     "Failed to intialize signal set (errno=%d).", errno);
    return -1;
  }

  /*
    Temporarily block all signals. New thread will inherit signal
    mask of the current thread.
  */
  if (pthread_sigmask(SIG_BLOCK, &set, &old_set))
    return -1;

  /* Create a helper thread. */
  rc= start_helper_thread();

  /* Restore the signal mask. */
  pthread_sigmask(SIG_SETMASK, &old_set, NULL);

  return rc;
}


/**
  Release any resources that were allocated as part of initialization.
*/

void
my_timer_deinitialize(void)
{
  /* Kill helper thread. */
  pthread_kill(timer_notify_thread.thread, MY_TIMER_KILL_SIGNO);

  /* Wait for helper thread termination. */
  my_thread_join(&timer_notify_thread, NULL);
}


/**
  Create a timer object.

  @param  timer   Location where the timer ID is returned.

  @return On success, 0.
          On error, -1 is returned, and errno is set to indicate the error.
*/

int
my_timer_create(my_timer_t *timer)
{
  struct sigevent sigev;

  memset(&sigev, 0, sizeof(sigev));

  sigev.sigev_value.sival_ptr= timer;
  sigev.sigev_signo= MY_TIMER_EVENT_SIGNO;
  sigev.sigev_notify= SIGEV_SIGNAL | SIGEV_THREAD_ID;
  sigev.sigev_notify_thread_id= timer_notify_thread_id;

  return timer_create(CLOCK_MONOTONIC, &sigev, &timer->id);
}
#elif defined(HAVE_SIGEV_PORT)
/**
  Timer expiration notification thread.

  @param  arg   Barrier object.
*/

static void *
timer_notify_thread_func(void *arg MY_ATTRIBUTE((unused)))
{
  port_event_t port_event;
  my_timer_t *timer;

  my_thread_init();

  while (1)
  {
    if (port_get(port_id, &port_event, NULL))
      break;

    if (port_event.portev_source != PORT_SOURCE_TIMER)
      continue;

    timer= (my_timer_t*)port_event.portev_user;
    timer->notify_function(timer);
  }

  my_thread_end();

  return NULL;
}


/**
  Create a helper thread to dispatch timer expiration notifications.

  @return On success, 0. On error, -1 is returned.
*/

static int
start_helper_thread(void)
{
  if (mysql_thread_create(key_thread_timer_notifier, &timer_notify_thread,
                          NULL, timer_notify_thread_func, NULL))
  {
    my_message_local(ERROR_LEVEL,
                     "Failed to create timer notify thread (errno= %d).",
                     errno);
    return -1;
  }

  return 0;
}


/**
  Initialize internal components.

  @return On success, 0.
          On error, -1 is returned, and errno is set to indicate the error.
*/

int
my_timer_initialize(void)
{
  int rc;

  if ((port_id= port_create()) < 0)
  {
    my_message_local(ERROR_LEVEL, "Failed to create port (errno= %d).", errno);
    return -1;
  }

  /* Create a helper thread. */
  rc= start_helper_thread();

  return rc;
}


/**
  Release any resources that were allocated as part of initialization.
*/

void
my_timer_deinitialize(void)
{
  assert(port_id >= 0);

  // close port
  close(port_id);

  /* Wait for helper thread termination. */
  my_thread_join(&timer_notify_thread, NULL);
}


/**
  Create a timer object.

  @param  timer   Location where the timer ID is returned.

  @return On success, 0.
          On error, -1 is returned, and errno is set to indicate the error.
*/

int
my_timer_create(my_timer_t *timer)
{
  struct sigevent sigev;
  port_notify_t port_notify;

  port_notify.portnfy_port= port_id;
  port_notify.portnfy_user= timer;

  memset(&sigev, 0, sizeof(sigev));
  sigev.sigev_value.sival_ptr= &port_notify;
  sigev.sigev_notify= SIGEV_PORT;

  return timer_create(CLOCK_REALTIME, &sigev, &timer->id);
}
#endif


/**
  Set the time until the next expiration of the timer.

  @param  timer   Timer object.
  @param  time    Amount of time (in milliseconds) before the timer expires.

  @return On success, 0.
          On error, -1 is returned, and errno is set to indicate the error.
*/

int
my_timer_set(my_timer_t *timer, unsigned long time)
{
  const struct itimerspec spec= {
    .it_interval= {.tv_sec= 0, .tv_nsec= 0},
    .it_value= {.tv_sec= time / 1000,
                .tv_nsec= (time % 1000) * 1000000}
  };

  return timer_settime(timer->id, 0, &spec, NULL);
}


/**
  Cancel the timer.

  @param  timer   Timer object.
  @param  state   The state of the timer at the time of cancellation, either
                  signaled (false) or nonsignaled (true).

  @return On success, 0.
          On error, -1 is returned, and errno is set to indicate the error.
*/

int
my_timer_cancel(my_timer_t *timer, int *state)
{
  int status;
  struct itimerspec old_spec;

  /* A zeroed initial expiration value disarms the timer. */
  const struct timespec zero_time= { .tv_sec= 0, .tv_nsec= 0 };
  const struct itimerspec zero_spec= { .it_value= zero_time };

  /*
    timer_settime returns the amount of time before the timer
    would have expired or zero if the timer was disarmed.
  */
  if (! (status= timer_settime(timer->id, 0, &zero_spec, &old_spec)))
    *state= (old_spec.it_value.tv_sec || old_spec.it_value.tv_nsec);

  return status;
}


/**
  Delete a timer object.

  @param  timer   Timer object.
*/

void
my_timer_delete(my_timer_t *timer)
{
  timer_delete(timer->id);
}

