/*                               -*- Mode: C -*- 
 * cwgeom.c -- Introduce a new disk to Clockwise.
 * 
 * Author          : 
 * Created On      : Mon Mar 22 08:29:26 1999
 * Last Modified By: 
 * Last Modified On: Fri Oct  1 08:48:40 1999
 * Status          : Unknown, Use with caution!
 * 
 * Unless other notices are present in any part of this file
 * explicitly claiming copyrights for other people and/or 
 * organizations, the contents of this file is fully copyright 
 * (C) 1999 Peter Bosch, all rights reserved.
 */

#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sched.h>
#include <string.h>
#include <sys/sysmacros.h>

#include "cwif.h"
#define MAXARGS	20

typedef struct {
  int	    	t_seektablen;
  cwsetime_t 	*t_seektab;
  struct {
    int		tz_readtablen;
    cwiotime_t	*tz_readtab;
    int		tz_writetablen;
    cwiotime_t	*tz_writetab;
  } t_zone[NUMZONES];
} performance_t;

static char *clockwise = CLOCKWISE;
static char *geometry = CWGEOMETRY;
static char *progname;
static int  lineno;
static int  verbose;

extern int optind;
extern char *optarg;

static void
usage()
{
  fprintf(stderr, "Usage: %s [-c clockwise] [-g geometry] [-v]\n",
	  progname);
  exit(1);
}

static int
parse_line(char *line, char *argv[], int maxargs)
{
  char *s, *_s;
  int index = 0;
  
  s = line;
  while (index < maxargs) {

    argv[index++] = s;
    _s = strchr(s, ' ');
    if (_s == NULL) {
      _s = strchr(s, '\t');
      if (_s == NULL)
	break;
    }
    s = _s;
    *s++ = '\0';
  }
  return index;
}

static void
installdisk(int cw, cwgeometry_t *geom, performance_t *performance)
{
  int n;

  if (geom->ge_nheads) {

    if (verbose) {
      char d[CW_MAXDTYPE + 1];
      int n;
    
      strncpy(d, geom->ge_dtype, CW_MAXDTYPE);
      d[CW_MAXDTYPE] = '\0';
      printf("%s, NHEADS %d, NCYLS %d\n", d, geom->ge_nheads, geom->ge_ncyls);
      for (n = 0; n != geom->ge_nzones; n++)
	printf("Zone [%d]: %08d->%08d, SPT %03d, NTRACKS %04d\n", n, 
	       geom->ge_zones[n].gez_start, 
	       geom->ge_zones[n].gez_start + geom->ge_zones[n].gez_spt * 
	         geom->ge_zones[n].gez_ntracks + geom->ge_nheads,
	       geom->ge_zones[n].gez_spt,
	       geom->ge_zones[n].gez_ntracks);
    }

    if (ioctl(cw, CW_GEOMETRY, geom) < 0) {
      geom->ge_dtype[CW_MAXDTYPE - 1] = '\0';
      fprintf(stderr, "%s: Cannot download disk config for %s: %s\n",
	      progname, geom->ge_dtype, strerror(errno));
    }
  }

  /* Make sure the performance table is consistent */
  if (performance->t_seektab) {
    cwseektimes_t *st;

    for (n = 0; n != geom->ge_nzones; n++)
      if (performance->t_zone[n].tz_readtab == NULL ||
	  performance->t_zone[n].tz_writetab == NULL) 
	break;

    if (n != geom->ge_nzones) {
      fprintf(stderr, "%s: Missing IO times for zone %d\n", 
	      progname, n);
      goto done;
    }

    /* Install the performance information in Clockwise */
    st = (cwseektimes_t *)malloc(sizeof(cwseektimes_t) +
				 (performance->t_seektablen - 1) * 
				 sizeof(cwsetime_t));
    if (st == NULL) {
      fprintf(stderr, "%s: Cannot allocate seek times table (size %d)\n",
	      progname, sizeof(cwseektimes_t) +
	      (performance->t_seektablen - 1) * sizeof(cwsetime_t));
      goto done;
    }

    memset(st, 0, sizeof(cwseektimes_t) +
	   (performance->t_seektablen - 1) * sizeof(cwsetime_t));

    strncpy(st->st_dtype, geom->ge_dtype, CW_MAXDTYPE);
    st->st_ne = performance->t_seektablen;
    memcpy(st->st_e, performance->t_seektab, 
	   performance->t_seektablen * sizeof(cwsetime_t));

    if (ioctl(cw, CW_SEEKTIME, st) < 0)
      fprintf(stderr, "%s: Cannot set seek table in Clockwise: %s\n",
	      progname, strerror(errno));
    free(st);

    /* Install all of the zones */
    for (n = 0; n != geom->ge_nzones; n++) {
      cwiotimes_t *iotime;

      iotime = 
	(cwiotimes_t *)malloc(sizeof(cwiotimes_t) +
			      (performance->t_zone[n].tz_readtablen - 1) * 
			      sizeof(cwiotime_t));
      if (iotime == NULL) {
	fprintf(stderr, "%s: Cannot allocate read IO times table (size %d)\n",
		progname, sizeof(cwiotimes_t) +
		(performance->t_zone[n].tz_readtablen - 1) * 
		sizeof(cwiotime_t));
	goto done;
      }

      memset(iotime, 0, sizeof(cwiotimes_t) +
	     (performance->t_zone[n].tz_readtablen - 1) * sizeof(cwiotime_t));

      strncpy(iotime->iot_dtype, geom->ge_dtype, CW_MAXDTYPE);
      iotime->iot_zone = n;
      iotime->iot_ne   = performance->t_zone[n].tz_readtablen;
      memcpy(iotime->iot_e, performance->t_zone[n].tz_readtab, 
	     performance->t_zone[n].tz_readtablen * sizeof(cwiotime_t));

      if (ioctl(cw, CW_IORTIME, st) < 0)
	fprintf(stderr, "%s: Cannot set read times for zone %d: %s\n",
		progname, n, strerror(errno));
      free(iotime);

      iotime = 
	(cwiotimes_t *)malloc(sizeof(cwiotimes_t) +
			      (performance->t_zone[n].tz_writetablen - 1) * 
			      sizeof(cwiotime_t));
      if (iotime == NULL) {
	fprintf(stderr, "%s: Cannot allocate write IO times table (size %d)\n",
		progname, sizeof(cwiotimes_t) +
		(performance->t_zone[n].tz_writetablen - 1) * 
		sizeof(cwiotime_t));
	goto done;
      }

      memset(iotime, 0, sizeof(cwiotimes_t) +
	     (performance->t_zone[n].tz_writetablen - 1) * sizeof(cwiotime_t));

      strncpy(iotime->iot_dtype, geom->ge_dtype, CW_MAXDTYPE);
      iotime->iot_zone = n;
      iotime->iot_ne   = performance->t_zone[n].tz_writetablen;
      memcpy(iotime->iot_e, performance->t_zone[n].tz_writetab, 
	     performance->t_zone[n].tz_writetablen * sizeof(cwiotime_t));

      if (ioctl(cw, CW_IOWTIME, st) < 0)
	fprintf(stderr, "%s: Cannot set write times for zone %d: %s\n",
		progname, n, strerror(errno));
      free(iotime);
    }
  }

done:
  memset(geom, 0, sizeof(cwgeometry_t));
  if (performance->t_seektab)
    free(performance->t_seektab);

  for (n = 0; n != NUMZONES; n++) {
    if (performance->t_zone[n].tz_readtab)
      free(performance->t_zone[n].tz_readtab);
    if (performance->t_zone[n].tz_writetab)
      free(performance->t_zone[n].tz_writetab);
  }
  memset(performance, 0, sizeof(performance_t));
}

static void
_disk(int argc, char **argv, int cw, cwgeometry_t *geom, 
      performance_t *performance)
{
  if (argc != 2) {
    fprintf(stderr, "%s: Illegal configuration line %d (%s)\n",
	    progname, lineno, argv[0]);
    return;
  }

  installdisk(cw, geom, performance);
  strncpy(geom->ge_dtype, argv[1], CW_MAXDTYPE);
}

static void
_nheads(int argc, char **argv, int cw, cwgeometry_t *geom,
	performance_t *performance)
{
  if (argc != 2) {
    fprintf(stderr, "%s: Illegal configuration line %d (%s)\n",
	    progname, lineno, argv[0]);
    return;
  }

  if (geom->ge_nheads != 0) {
    fprintf(stderr, "%s: Number of heads already set in line %d\n", 
	    progname, lineno);
    return;
  }

  geom->ge_nheads = (uint32_t)strtol(argv[1], (char **)NULL, 0);
}

static void
_ncyls(int argc, char **argv, int cw, cwgeometry_t *geom, 
       performance_t *performance)
{
  FILE *fp;
  int _lineno, allocated, used;
  cwsetime_t *seektab;

  if (argc != 3) {
    fprintf(stderr, "%s: Illegal configuration line %d (%s)\n",
	    progname, lineno, argv[0]);
    return;
  }

  if (geom->ge_ncyls != 0) {
    fprintf(stderr, "%s: Number of cylinders already set in line %d\n", 
	    progname, lineno);
    return;
  }

  geom->ge_ncyls = (uint32_t)strtol(argv[1], (char **)NULL, 0);

  fp = fopen(argv[2], "r");
  if (fp == NULL) {
    fprintf(stderr, "%s: Cannot open %s: %s\n", progname, argv[2],
	    strerror(errno));
    return;
  }
  
#define NUMCYLS 1000
  seektab = (cwsetime_t *)malloc(NUMCYLS * sizeof(cwsetime_t));
  if (seektab == NULL) {
    fclose(fp);
    fprintf(stderr, "%s: Cannot allocate seek table (size %d)\n",
	    progname, NUMCYLS * sizeof(cwsetime_t));
    return;
  }
  allocated = NUMCYLS;
  used      = 0;

  _lineno = 1;
  while (1) {
    int _argc, n;
    char _line[256], *_argv[MAXARGS], *s;

    memset(_line, 0, 256);
    if (fgets(_line, 255, fp) == NULL)
      break;
    if (strlen(_line) == 0) break;

    if (_line[0] == '#') continue;

    s = strchr(_line, '\n');
    if (s) *s = '\0';
    if (strlen(_line) == 0) continue;

    _argc = parse_line(_line, _argv, MAXARGS);
    if (_argc != 2) {	
      fprintf(stderr, "%s: Invalid line seek file %s: %s\n",
	      progname, argv[2], _line);
      continue;
    }
    
    if (used == allocated) {
      seektab = (cwsetime_t *)realloc(seektab, (allocated + NUMCYLS) *
				      sizeof(cwsetime_t));
      if (seektab == NULL) {
	fclose(fp);
	fprintf(stderr, "%s: Cannot allocate seek table (size %d)\n",
		progname, (allocated + NUMCYLS) * sizeof(cwsetime_t));
	return;
      }
      allocated += NUMCYLS;
    }

    seektab[used].se_t  = US(strtol(_argv[0], (char **)NULL, 0));
    seektab[used].se_nt = strtol(_argv[1], (char **)NULL, 0);
    used++;
    _lineno++;
  }
  fclose(fp);

  if (used) {
    performance->t_seektablen = used;
    performance->t_seektab    = seektab;
  }
  else
    free(seektab);
}

static void
_nzones(int argc, char **argv, int cw, cwgeometry_t *geom,
	performance_t *performance)
{
  if (argc != 2) {
    fprintf(stderr, "%s: Illegal configuration line %d (%s)\n",
	    progname, lineno, argv[0]);
    return;
  }

  if (geom->ge_nzones != 0) {
    fprintf(stderr, "%s: Number of zones already set in line %d\n", 
	    progname, lineno);
    return;
  }

  geom->ge_nzones = (uint32_t)strtol(argv[1], (char **)NULL, 0);
  if (geom->ge_nzones > NUMZONES) {
    fprintf(stderr, "%s: Too many zones.  Line %d, nzones %d, max %d\n",
	    progname, lineno, geom->ge_nzones, NUMZONES);
    geom->ge_nzones = 0;
    return;
  }
}

static void
_rpm(int argc, char **argv, int cw, cwgeometry_t *geom, 
     performance_t *performance)
{
  if (argc != 2) {
    fprintf(stderr, "%s: Illegal configuration line %d (%s)\n",
	    progname, lineno, argv[0]);
    return;
  }

  if (geom->ge_rpm != 0) {
    fprintf(stderr, "%s: Rotational speed already set in line %d\n", 
	    progname, lineno);
    return;
  }

  geom->ge_rpm = (uint32_t)strtol(argv[1], (char **)NULL, 0);
}

static void
_zone(int argc, char **argv, int cw, cwgeometry_t *geom, 
      performance_t *performance)
{
  int zone;
  FILE *fp;
  int _lineno, allocated, used;
  cwiotime_t *iotime;

  if (argc != 7) {
    fprintf(stderr, "%s: Illegal configuration line %d (%s)\n",
	    progname, lineno, argv[0]);
    return;
  }

  zone = strtol(argv[1], (char **)NULL, 0);
  if (zone < 0 || zone >= geom->ge_nzones) {
    fprintf(stderr, "%s: Zone out of range in line %d.  Zone %d, max zone %d\n",
	    progname, lineno, zone, geom->ge_nzones);
    return;
  }

  if (geom->ge_zones[zone].gez_start   != 0 ||
      geom->ge_zones[zone].gez_spt     != 0 ||
      geom->ge_zones[zone].gez_ntracks != 0) {
    fprintf(stderr, "%s: Start (%d)/SPT (%d)/NT (%d) for zone %d already set.  Line %d\n", 
	    progname, geom->ge_zones[zone].gez_start,
	    geom->ge_zones[zone].gez_spt, geom->ge_zones[zone].gez_ntracks,
	    zone, lineno);
    return;
  }
  geom->ge_zones[zone].gez_start   = strtol(argv[2], (char **)NULL, 0);
  geom->ge_zones[zone].gez_spt     = strtol(argv[3], (char **)NULL, 0);
  geom->ge_zones[zone].gez_ntracks = strtol(argv[4], (char **)NULL, 0);

  /* Parse the performance files */
  fp = fopen(argv[5], "r");
  if (fp == NULL) {
    fprintf(stderr, "%s: Cannot open %s: %s\n", progname, argv[5],
	    strerror(errno));
    return;
  }

#define NUMIOS 50
  iotime = (cwiotime_t *)malloc(NUMIOS * sizeof(cwiotime_t));
  if (iotime == NULL) {
    fclose(fp);
    fprintf(stderr, "%s: Cannot allocate IO table (size %d)\n",
	    progname, NUMIOS * sizeof(cwiotime_t));
    return;
  }
  allocated = NUMIOS;
  used      = 0;

  _lineno = 1;
  while (1) {
    int _argc, n;
    char _line[256], *_argv[MAXARGS], *s;

    memset(_line, 0, 256);
    if (fgets(_line, 255, fp) == NULL)
      break;
    if (strlen(_line) == 0) break;

    if (_line[0] == '#') continue;

    s = strchr(_line, '\n');
    if (s) *s = '\0';
    if (strlen(_line) == 0) continue;

    _argc = parse_line(_line, _argv, MAXARGS);
    if (_argc != 2) {	
      fprintf(stderr, "%s: Invalid line read I/O time file %s: %s\n",
	      progname, argv[5], _line);
      continue;
    }
    
    if (used == allocated) {
      iotime = (cwiotime_t *)realloc(iotime, (allocated + NUMIOS) *
				      sizeof(cwiotime_t));
      if (iotime == NULL) {
	fclose(fp);
	fprintf(stderr, "%s: Cannot allocate read I/O time table (size %d)\n",
		progname, (allocated + NUMIOS) * sizeof(cwiotime_t));
	return;
      }
      allocated += NUMIOS;
    }

    iotime[used].io_bs = strtol(_argv[0], (char **)NULL, 0);
    iotime[used].io_t  = US(strtol(_argv[1], (char **)NULL, 0));
    used++;
    _lineno++;
  }
  fclose(fp);

  if (used) {
    performance->t_zone[zone].tz_readtablen = used;
    performance->t_zone[zone].tz_readtab    = iotime;
  }
  else
    free(iotime);

  fp = fopen(argv[6], "r");
  if (fp == NULL) {
    fprintf(stderr, "%s: Cannot open %s: %s\n", progname, argv[6],
	    strerror(errno));
    return;
  }

  iotime = (cwiotime_t *)malloc(NUMIOS * sizeof(cwiotime_t));
  if (iotime == NULL) {
    fclose(fp);
    fprintf(stderr, "%s: Cannot allocate IO table (size %d)\n",
	    progname, NUMIOS * sizeof(cwiotime_t));
    return;
  }
  allocated = NUMIOS;
  used      = 0;

  _lineno = 1;
  while (1) {
    int _argc, n;
    char _line[256], *_argv[MAXARGS], *s;

    memset(_line, 0, 256);
    if (fgets(_line, 255, fp) == NULL)
      break;
    if (strlen(_line) == 0) break;

    if (_line[0] == '#') continue;

    s = strchr(_line, '\n');
    if (s) *s = '\0';
    if (strlen(_line) == 0) continue;

    _argc = parse_line(_line, _argv, MAXARGS);
    if (_argc != 2) {	
      fprintf(stderr, "%s: Invalid line write I/O time file %s: %s\n",
	      progname, argv[6], _line);
      continue;
    }
    
    if (used == allocated) {
      iotime = (cwiotime_t *)realloc(iotime, (allocated + NUMIOS) *
				     sizeof(cwiotime_t));
      if (iotime == NULL) {
	fclose(fp);
	fprintf(stderr, "%s: Cannot allocate read I/O time table (size %d)\n",
		progname, (allocated + NUMIOS) * sizeof(cwiotime_t));
	return;
      }
      allocated += NUMIOS;
    }

    iotime[used].io_bs = strtol(_argv[0], (char **)NULL, 0);
    iotime[used].io_t  = US(strtol(_argv[1], (char **)NULL, 0));
    used++;
    _lineno++;
  }
  fclose(fp);

  if (used) {
    performance->t_zone[zone].tz_writetablen = used;
    performance->t_zone[zone].tz_writetab    = iotime;
  }
  else
    free(iotime);
}

static struct {
  char *keyword;
  void (*f)(int, char **, int, cwgeometry_t *, performance_t *performance);
} cw_commands[] = {
  { "disk", _disk },
  { "rpm", _rpm },
  { "nheads", _nheads },
  { "ncyls", _ncyls },
  { "nzones", _nzones },
  { "zone", _zone },
};

static void
_geometry(FILE *fp, int cw)
{
  cwgeometry_t geom;
  performance_t performance;

  memset(&geom, 0, sizeof(cwgeometry_t));
  memset(&performance, 0, sizeof(performance_t));

  while (1) {
    int argc, n;
    char line[256], *argv[MAXARGS], *s;

    memset(line, 0, 256);
    if (fgets(line, 255, fp) == NULL)
      break;
    if (strlen(line) == 0) break;

    if (line[0] == '#') continue;

    s = strchr(line, '\n');
    if (s) *s = '\0';
    if (strlen(line) == 0) continue;

    argc = parse_line(line, argv, MAXARGS);
    if (argc < 1) {	
      fprintf(stderr, "%s: Invalid line in configuration file %s: %s\n",
	      progname, geometry, line);
      continue;
    }
    
    for (n = 0; n != sizeof(cw_commands) / sizeof(cw_commands[0]); n++)
      if (!strcmp(cw_commands[n].keyword, argv[0])) {
	(cw_commands[n].f)(argc, argv, cw, &geom, &performance);
	break;
      }

    if (n == sizeof(cw_commands) / sizeof(cw_commands[0]))
      fprintf(stderr, "%s: Invalid line %d in %s: Unknown command %s\n",
	      progname, lineno, geometry, argv[0]);
    lineno++;
  }

  installdisk(cw, &geom, &performance);
}

int
main(int argc, char **argv)
{
  int opt, fd;
  FILE *fp;

  progname = argv[0];
  while ((opt = getopt(argc, argv, "vc:g:?")) != EOF) {
    switch (opt) {
    case 'v':
      verbose = 1;
      break;
    case 'c':
      clockwise = optarg;
      break;
    case 'g':
      geometry = optarg;
      break;
    case '?':
      usage();
    }
  }
  if (optind != argc) usage();

  fp = fopen(geometry, "r");
  if (fp == NULL) {
    fprintf(stderr, "%s: Cannot open %s: %s\n",
	    progname, geometry, strerror(errno));
    exit(1);
  }

  fd = open(clockwise, O_RDONLY);
  if (fd < 0) {
    fprintf(stderr, "%s: Cannot open %s: %s\n",
	    progname, clockwise, strerror(errno));
    exit(1);
  }

  _geometry(fp, fd);

  (void)close(fd);
  fclose(fp);
  exit(0);
}
