/*
 *      Copyright (c) 2003 Alexander Bartolich
 *
 * 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.
 *
 * gcc -Wall -O2 -o timeslice -s timeslice.c
 */
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>

#include <sys/types.h>
#include <sys/wait.h>

#define PACKAGE		"timeslice"
#define VERSION		"2001-02-18"

static const char sz_nothing_to_do[] =
  "Nothing to do. Specify option -h for help.\n";
static const char sz_unk_option[] =
  "Unknown option '%s' in argument %d.\n";
static const char sz_missing_arg[] =
  "Missing argument %d.\n";
static const char sz_inv_number[] =
  "Invalid number in argument %d at '%s'.\n";
static const char sz_usage[] =
  "USAGE\n"
  "  " PACKAGE " [OPTION]... [COMMAND [ARG]...]\n"
  "\n"
  "OPTION\n"
  "  -a milli-seconds    length of active slice\n"
  "  -i milli-seconds    length of inactive slice\n"
  "  -h                  print this help and exit\n"
  "  -V                  print version and exit\n"
  ;
static const char sz_version[] =
  PACKAGE " " VERSION "\n";

static const char sz_proc_signaled[] =
  "Process %u was signaled to exit. Status code is %d.\n";
static const char sz_proc_terminated[] =
  "Process %u terminated with code %d.\n";

typedef enum { SLICE_ACTIVE, SLICE_SUSPENDED, SLICE_NR } SLICE_TYPE;

static struct { unsigned long usec; int signo; } slice[SLICE_NR] =
{
  { 500000, SIGSTOP },
  { 500000, SIGCONT }
};

int write_pipe = -1;
pid_t child_pid;

static char**
read_options(char** argv)
{
  char** pp = argv;
  for(;;)
  {
    const char* p;
    unsigned long* usec;
    char* endp;
    
    p = *++pp;
    if (p == 0)
    {
      fputs(sz_nothing_to_do, stderr);
      return 0;
    }
    if (p[0] != '-')
      return pp;
    switch(*++p)
    {
      case 'a':
        usec = &slice[SLICE_ACTIVE].usec; 
	break;
      case 'i':
        usec = &slice[SLICE_SUSPENDED].usec; 
	break;
      case 'h':
        puts(sz_version);
        fputs(sz_usage, stdout);
	return 0;
      case 'V':
        fputs(sz_version, stdout);
	return 0;
      default:
        fprintf(stderr, sz_unk_option, p, (int)(pp - argv));
        return 0;
    }
    p = *++pp;
    if (p == 0)
    {
      fprintf(stderr, sz_missing_arg, (int)(pp - argv));
      return 0;
    }
    *usec = strtoul(p, &endp, 0);
    if (*endp != 0)
    {
      fprintf(stderr, sz_inv_number, (int)(pp - argv), endp);
      return 0;
    }
  }
}

static void
sig_child(int signo, siginfo_t* si, void* p)
{
  int status;

  assert(child_pid > 0);
  while(0 > waitpid(child_pid, &status, 0))
  {
    if (errno != EINTR)
    {
      perror("wait(2) failed");
      exit(-1);
    }
  }
  if (WIFSIGNALED(status))
    fprintf(stderr, sz_proc_signaled, child_pid, WEXITSTATUS(status));
  else if (WEXITSTATUS(status))
    fprintf(stderr, sz_proc_terminated, child_pid, WEXITSTATUS(status));
  exit(0);
}

static int
set_signals(void)
{
  struct sigaction sa;

  sa.sa_handler = 0;
  sa.sa_sigaction = sig_child;
  sigemptyset(&sa.sa_mask);
  sa.sa_flags = SA_NOCLDSTOP | SA_SIGINFO;
  if (0 > sigaction(SIGCHLD, &sa, 0))
  {
    perror("sigaction(2) failed");
    return -1;
  }
  return 0;
}

int
main(int argc, char** argv)
{
  char** argv_exec;
  SLICE_TYPE type;
 
  assert(argv[argc] == 0);
  argv_exec = read_options(argv);
  if (0 == argv_exec)
    return -1;

  if (0 > set_signals())
    return -1;

  child_pid = fork();
  if (child_pid < 0)
  {
    perror("fork(2) failed");
    return -1;
  }
  if (child_pid == 0)
  {
  /*
   * child process
   */ 
    assert(*argv_exec != 0);
    execvp(*argv_exec, argv_exec);
    perror(*argv_exec);
    return -1;
  }
  /*
   * parent process
   */ 
  type = SLICE_ACTIVE;
  for(;;)
  {
    usleep(slice[type].usec);
    if (0 > kill(child_pid, slice[type].signo))
    {
      perror("kill(2) failed");
      return -1;
    }
    type = (type + 1) % SLICE_NR;
  }
}
