/*
   "ipc.c" - part of "RAGMAAN, een anagrammengenerator"

   Copyright (C) 1999 Raymond Zandbergen

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   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 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., 675 Mass Ave, Cambridge, MA 02139, USA.

   The maintainer of this program is Raymond Zandbergen (ray@wirehub.nl)
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <string.h>

#include "ipc.h"

/* this if/endif was copied from the smctl man page ... */

#if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)
/* union semun is defined by including <sys/sem.h> */
#else
/* according to X/OPEN we have to define it ourselves */
union semun
  {
    int val;			/* value for SETVAL */
    struct semid_ds *buf;	/* buffer for IPC_STAT, IPC_SET */
    unsigned short int *array;	/* array for GETALL, SETALL */
    struct seminfo *__buf;	/* buffer for IPC_INFO */
  };
#endif

SHM_POOL_T *shm;

void
sem_down_id (int sem_id, int sem_num)
{
  struct sembuf sem_stuff;

  sem_stuff.sem_num = sem_num;
  sem_stuff.sem_op = -1;
  sem_stuff.sem_flg = 0;

  if (semop (sem_id, &sem_stuff, 1))
    {
      perror ("semop (neer) failed");
      exit (-1);
    }
}

int
tsem_down_id (int sem_id, int sem_num, int sec, int usec)
{
  struct itimerval delay_time;

  struct sembuf sem_stuff;
  int result;

  sem_stuff.sem_num = sem_num;
  sem_stuff.sem_op = -1;
  sem_stuff.sem_flg = 0;

  delay_time.it_interval.tv_sec = 0;
  delay_time.it_interval.tv_usec = 0;
  delay_time.it_value.tv_sec = sec;
  delay_time.it_value.tv_usec = usec;

  setitimer (ITIMER_REAL, &delay_time, 0);

  result = semop (sem_id, &sem_stuff, 1);

  if (result == -1 && errno == EINTR)
    {
      return -1;
    }

  delay_time.it_value.tv_sec = delay_time.it_value.tv_usec = 0;
  setitimer (ITIMER_REAL, &delay_time, 0);

  if (result == -1)
    {
      perror (" semop (neer, tsem_down_id) failed");
      exit (-1);
    }
  return 0;
}

int
ssem_down_id (int sem_id, int sem_num)
{
  struct sembuf sem_stuff;
  int result;

  sem_stuff.sem_num = sem_num;
  sem_stuff.sem_op = -1;
  sem_stuff.sem_flg = 0;

  signal (SIGUSR1, sigusr1_handler);
  result = semop (sem_id, &sem_stuff, 1);

  if (result == -1 && errno == EINTR)
    {
      return -1;
    }

  if (result == -1)
    {
      perror (" semop (neer, tsem_down_id) failed");
      exit (-1);
    }
  return 0;
}

void
sem_up_id (int sem_id, int sem_num)
{
  struct sembuf sem_stuff;

  sem_stuff.sem_num = sem_num;
  sem_stuff.sem_op = 1;
  sem_stuff.sem_flg = 0;

  if (semop (sem_id, &sem_stuff, 1))
    {
      perror ("semop (op) failed");
      exit (-1);
    }
}

void
sem_set_id (int sem_id, int sem_num, int sem_val)
{
  union semun sem_stuff;

  sem_stuff.val = sem_val;

  if (semctl (sem_id, sem_num, SETVAL, sem_stuff))
    {
      perror ("semctl failed");
      exit (-1);
    }
}

int
sem_create (int nof_semaphores)
{
  int sem_id;

  sem_id = semget (IPC_PRIVATE, nof_semaphores, IPC_CREAT | 00600);
  if (sem_id == -1)
    {
      perror ("could not get semaphore queue");
      exit (-1);
    }
  return sem_id;
}

void
sem_remove (int sem_id)
{
  union semun sem_stuff;	/* dummy */

  if (semctl (sem_id, 0, IPC_RMID, sem_stuff))
    {
      perror ("sem_remove: semctl failed");
      exit (-1);
    }
}

int
shm_get (void)
{
  int shm_id;

  shm_id = shmget (IPC_PRIVATE, sizeof (SHM_POOL_T), IPC_CREAT | 00600);

  if (shm_id == -1)
    {
      perror ("could not get shared mem");
      exit (-1);
    }
  return shm_id;
}

SHM_POOL_T *
shm_attach (int shm_id)
{
  return (SHM_POOL_T *) shmat (shm_id, 0, 0);
}

void
shm_detach (SHM_POOL_T * shm)
{
  shmdt ((char *) shm);
}

void
shm_remove (int shm_id)
{
  struct shmid_ds dummy_shm_buf;

  shmctl (shm_id, IPC_RMID, &dummy_shm_buf);
}

void
server_command (int *progress)
{
  static int waiting = 0;
  sigset_t set;

  sigemptyset (&set);
  sigaddset (&set, SIGUSR2);
  sigaddset (&set, SIGCHLD);

  if (!progress)
    {
      sigaddset (&set, SIGUSR1);
      sigprocmask (SIG_BLOCK, &set, 0);
      shm_lock ();
      shm->cmd.new = 1;
      sem_set (NEW_CMD, 1);
      shm_unlock ();
      sem_down (CMD_ACK);
    }
  else
    {
      sigprocmask (SIG_BLOCK, &set, 0);
      if (!waiting)
	{
	  shm_lock ();
	  shm->cmd.new = 1;
	  sem_set (NEW_CMD, 1);
	  waiting = 1;
	  shm_unlock ();
	}
      if (-1 == ssem_down (CMD_ACK))
	{
	  *progress = shm->progress;
	}
      else
	{
	  waiting = 0;
	  *progress = -1;
	}
    }
  sigprocmask (SIG_UNBLOCK, &set, 0);
}

int
read_command (int block_flag)
{
  int return_value = 0;

  if (block_flag == NONBLOCKING)
    {

      shm_lock ();
      if (shm->cmd.new)
	{
	  sem_set (NEW_CMD, 0);
	  return_value = 1;
	  shm->cmd.new = 0;
	}
      shm_unlock ();
      return return_value;
    }

/*
   shm->async_data.server_working = 0;
   kill(shm->client_pid, SIGUSR2);
 */

  sem_down (NEW_CMD);
  shm_lock ();
  shm->cmd.new = 0;
  return_value = 1;
  shm_unlock ();
  return return_value;
}

int
tread_command (int sec, int usec)
{
  if (-1 == tsem_down (NEW_CMD, sec, usec))
    {
      return 0;
    }
  shm_lock ();
  shm->cmd.new = 0;
  shm_unlock ();
  return 1;
}

static int server_running = 0;

pid_t
server_create (char *file_name,
	       CMD_T ** server_command, REPLY_T ** server_reply)
{
  if (!server_running)
    {
      pid_t pid;
      int i;
      int fatal_signal[] =
      {SIGHUP, SIGINT, SIGQUIT,
       SIGILL, SIGABRT, SIGBUS,
       SIGFPE, SIGSEGV, SIGTERM, 0};

      int shm_id = shm_get ();
      sigset_t set;

      shm = shm_attach (shm_id);
      shm->shm_id = shm_id;
      shm->sem_id = sem_create (NOF_SEMAPHORES);
      shm->server_running = 0;
      sem_set (SERVER_INIT, 0);

#ifndef	NDEBUG
      puts ("creating server");
#endif

      signal (SIGCHLD, sigchld_handler);

      sigemptyset (&set);
      sigaddset (&set, SIGUSR1);
      sigaddset (&set, SIGUSR2);
      sigaddset (&set, SIGCHLD);
      sigprocmask (SIG_BLOCK, &set, 0);
      for (i = 0; fatal_signal[i]; i++)
	{
	  signal (fatal_signal[i], die);
	}
      pid = fork ();

      if (pid == 0)
	{			/* child */

	  char shm_id_ascii[40];

	  sprintf (shm_id_ascii, "%d", shm_id);
	  execl (file_name, file_name, shm_id_ascii, 0);
	  fprintf (stderr, "server_create '%s':\n", file_name);
	  perror ("execl failed");
	  sem_up (SERVER_INIT);
	  exit (1);
	}

#ifndef	NDEBUG
      puts ("waiting for server_init");
#endif
      sem_down (SERVER_INIT);
      sigprocmask (SIG_UNBLOCK, &set, 0);
#ifndef	NDEBUG
      puts ("server_init done");
#endif
      if (!shm || shm->server_running == 0)
	{

	  /* something went wrong */

	  int dummy;
	  wait (&dummy);
	  server_running = 0;
	  return 0;
	}
      shm->server_pid = pid;
      shm->client_pid = getpid ();
      sem_set (LOCK_SHM, 1);
      *server_command = &(shm->cmd);
      *server_reply = &(shm->reply);
      server_running = 1;
      return pid;
    }
  return 0;
}

void
server_destroy (void)
{
  int dummy;

  if (server_running)
    {
      shm->cmd.cmd = CMD_QUIT;
      server_command (0);
      wait (&dummy);
      server_running = 0;
    }
}

void
report_progress (int progress)
{
  shm->progress = progress;
  kill (shm->client_pid, SIGUSR1);
}

void
sigusr1_handler (int dummy)
{
  signal (SIGUSR1, sigusr1_handler);
}

void
sigchld_handler (int dummy)
{
  cleanup ();
#ifndef	NDEBUG
  puts ("server exited");
#endif
}

void
cleanup (void)
{
  int shm_id;

  if (shm)
    {
      sem_remove (shm->sem_id);
      shm_id = shm->shm_id;
      shm_detach (shm);
      shm = 0;
      shm_remove (shm_id);
    }
}

void
die (int signal)
{
  fprintf (stderr, "got signal %d - cleanup time\n", signal);
  cleanup ();
  exit (1);
}
