/*
 *     yasc.c - Yet Another SETI@Home Controller.
 *     Copyright (C) 2000 Erik Forsberg
 *     forsberg@lysator.liu.se
 *
 *     Tweaked by Karsten Kruse 2002
 *     tecneeq@tecneeq.de www.tecneeq.de
 *
 *     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, see the file COPYING; if not, write
 *     to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge,
 *     MA 02139, USA.
 */

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#define VERSION "0.01.tec2"
#define CHECKINTERVALL "2"    /* in seconds */

struct clients;
struct clients {
    char *path;
    unsigned int pid;
    struct clients *next;
};

int verbose;

struct device;
struct device {
    char *name;
    unsigned long lastread;
    int changed;
    struct device *next;
};

void run(char *path)
{
    if (chdir(path) == -1) {
	perror("chdir");
	exit(1);
    }
    if (verbose)
	execlp("./setiathome", "setiathome", NULL);
    else
	execlp("./setiathome", "setiathome", NULL);
    perror("execv");
}

void usage(char *progname)
{
    printf("Usage: %s [OPTION] <setidir1> <setidir2> ...\n", progname);
    printf("Run one or more SETI@Home processes after a configurable amount of idle time\n at the keyboard or mouse");
    printf(" (Or any other device that has an entry in \n/proc/interrupts).\n\n");
    printf("   -i\tTime to pass before running client(s) (minutes)\n");
    printf("   -d\tDevice to watch. Pass several -d to watch several devices\n");
    printf("   -v\tBe verbose\n");
    printf("   -h\tShow this text.\n");
    printf("\n");
}


struct clients *fillinClients(int startidx, 
			      int endidx, 
			      char **argv)
{
    struct clients *p, *d;
    p = d = NULL;
    for(;startidx<=endidx;startidx++) {
	if (p == NULL) {
	    p = (struct clients *)malloc(sizeof(struct clients));
	    p -> next = NULL;
	    d = p;
	}
	else {
	    d = (struct clients *)malloc(sizeof(struct clients));
	    d -> next = p;
	    p = d;
	}
	p -> pid = 0;
	p -> path = argv[startidx-1];
    }
    return p;
}

struct device *newDevice(char *name, struct device *d)
{
    struct device *t;
    if (d == NULL) {
	d = (struct device *)malloc(sizeof(struct device));
	d-> next = NULL;
	t = d;
    }
    else {
	t = (struct device *)malloc(sizeof(struct device));
	t -> next = d;
	d = t;
    }
    d -> name = name;
    d -> lastread = 0;
    d -> changed = 0;
    return d;
}

int getNrofProcessors(void) 
{
    int ret = 0;
    char *ch;
    FILE *fp;
    char line[85];
    fp = fopen("/proc/cpuinfo", "r");
    if (fp == NULL) {
	printf("\nCouldn't open /proc/cpuinfo - are you sure you have /proc filesystem support?");
	exit(1);
    }
    while(fgets(line, 85, fp) != NULL) {
	if (strstr(line, "processor") != NULL) {
	    ch = strchr(line, ':');
	    ch++;
	    ret = atoi(ch);
	}
    }
    fclose(fp);
    return ret + 1;
}

int updateReadings(struct device *d, int nrof_processors)
{
    char *ch;
    FILE *fp;
    int i;
    int isUpdated = 0;
    struct device *base;
    unsigned long reading;
    char line[1024]; /* Support _MANY_ processors :) */
    fp = fopen("/proc/interrupts", "r");
    if (fp == NULL) {
	printf("\nCouldn't open /proc/interrupts - are you sure you have /proc filesystem support?");
	exit(1);
    }
    while(fgets(line, 1024, fp) != NULL) {
	base = d;
	while (base != NULL) {
	    if (strstr(line, base->name) == NULL) {
		base = base->next;
		continue;
	    }
	    ch = strchr(line, ':');
	    ch++;
	    reading = atol(ch);
	    if (nrof_processors > 1)
		for(i=1;i<nrof_processors;i++) {
		    while(*ch == ' ')
		    ch++;
		    while(*ch != ' ')
		    ch++;
		    reading += atol(ch);
		}      
	    if (reading > base->lastread) {
		base->changed = 1;
		isUpdated = 1;
	    }
	    else
		base->changed = 0;
		base->lastread = reading;
	    if(verbose)
		printf("Device %s had reading %d -%s updated\n", base->name, 
		    reading, base->changed ? "" : " not");
	    base = base->next;
	}
    }
    fclose(fp);
    return isUpdated;
}

int main(int argc, char **argv)
{
unsigned int idletime = 5, countdown;
  int nrof_processors = 0;
  struct clients *c = NULL;
  struct clients *tc = NULL;
  struct device *d = NULL;
  struct device *td = NULL;
  unsigned int pid = 0;
  int ch;
  verbose = 0;
  printf("YASC %s by Erik Forsberg, tweak by Karsten Kruse \n", VERSION);
  if (argc < 2) {
    usage(argv[0]);
    return -1;
  }
  while ((ch = getopt(argc, argv, "i:vd:h")) != -1) {
    switch(ch) {
    case (int)'i':
      idletime = atoi(optarg);
      break;
    case (int)'v':
      verbose = 1;
      break;
    case (int)'d':
      d = newDevice(optarg, d);
      break;
    case (int)'h':
      usage(argv[0]);
      return -1;
    default:
      printf(".\n");
      return -1;
    }
  }
  if (optind == argc) {
    printf("No clientdirs specified! Nothing to do!\n");
    return -1;
  }
  if(idletime == 0) {
    printf("Idletime set to 0 minutes - I only \nunderstand idletimes of 1 minute or more");
    return -1;
  }
  c = fillinClients(optind+1, argc, argv);
  nrof_processors = getNrofProcessors();
  if (verbose) {

    printf("Will execute SETI@home clients in the following dirs:\n");
    tc = c;
    while(tc!=NULL) {
      printf("%s\n", tc->path);
      tc = tc->next;
    }
    printf("You seem to have %d processors\n", nrof_processors);
    printf("I will wait for %d minute%s before executing clients\n", idletime, 
	   idletime > 1 ? "s" : "");
  }
  sleep(2); /* Get rid of keyboard interrupts */
  updateReadings(d, nrof_processors);
  countdown = idletime;
  sleep(60);
  while(1) {
    if (countdown > 0) {
      if (updateReadings(d, nrof_processors)) {
	countdown = idletime;
      }
      else {
	countdown --;
	if (countdown == 0) {
	  tc = c;
	  while (tc != NULL) {
	    if (verbose)
	      printf("\nForking of child..");
	    pid = fork();
	    if (pid != 0) {
	      tc->pid = pid;
	      tc = tc->next;
	      continue;
	    }
	    else 
	      run(tc->path); /* In child */
	  
	  }
	  continue;
	}
      }
    }
    else { /* KILL! */
      while (!updateReadings(d, nrof_processors)) {
	if(verbose)
	  printf("Not updated, sleeping short while..\n");
	sleep(2); /* We want to get rid of running processes FAST when */
	continue;  /* we get back to our terminal. */
      }
      tc = c;
      if (verbose)
	printf("Killing processes..\n");
      while (tc != NULL) {
	kill(tc->pid, SIGKILL);
	wait(NULL); /* Hocus Pocus to prevent <defunct> processes */
	tc = tc->next;
      }
      countdown = idletime;
    }
    sleep(60);
  } /* while(1)  */
} /* main() */

/* END */