/*
 * Array Test
 *
 * Regression test utility for raidreconf
 * (C) 2000, by Jakob Oestergaard
 *
 * This software is licensed under the GNU Public License.
 *
 * Usage:
 *     arytst {-w|-r} -l <raid level, {0,1,4,5,20,30}> -c <chunksize (blocks)> -s <array size (blocks)> <dev0> <dev1> ...
 * A block is 1024 bytes
 * Array level 20 :  linear
 * Array level 30 : single disk
 *
 * arrays are filled with 64-bit words holding
 *   [gblock number : 32 bit]
 *   [origin level : 8 bit]
 *   [origin disk-chunk : 16 bit]
 *   [padding : 8 bit]
 */

#define BLOCK_SIZE 1024

#include "common.h"
#include <popt.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

long array_size = 0;
long chunk_size = 0;
int array_level = -1;
char opt_write = 0, opt_read = 0;
char ** args = 0;

struct fillword {
  unsigned int block_num;
  char org_level;
  unsigned short org_dchunk;
  char padding;
};

#define GBLK_SIZE 1024
#define PADNUM 42

void print_fillword(const struct fillword*);
void init_fillword(struct fillword*, unsigned gblock, unsigned dchunk);
void init_block_buf(char*, unsigned gblock, unsigned dchunk);
int check_fillword(const struct fillword*, unsigned gblock, unsigned dchunk);
int check_block_buf(const char*, unsigned gblock, unsigned dchunk);

static void ary_progress(const char*, unsigned now, unsigned total);
static void endary_progress(const char*);

int write_single(void);
int read_single(void);

int write_raid0(void);
int read_raid0(void);

int main(int argc, const char** argv)
{  
  poptContext optCon;
  int i;
  struct poptOption optionsTable[] = {
    {"write", 'w', POPT_ARG_NONE, &opt_write, 0},
    {"read", 'r', POPT_ARG_NONE, &opt_read, 0},
    {"level", 'l', POPT_ARG_INT, &array_level, 0},
    {"chunksize", 'c', POPT_ARG_LONG, &chunk_size, 0},
    {"arraysize", 's', POPT_ARG_LONG, &array_size, 0},
    {0, 0, 0, 0, 0} 
  } ;
  
  /*
   * Parse options
   */
  optCon = poptGetContext("arytst", argc, argv, optionsTable, 0);
  if ((i = poptGetNextOpt(optCon)) < -1) {
    fprintf(stderr, "%s: %s\n", 
	    poptBadOption(optCon, POPT_BADOPTION_NOALIAS),
	    poptStrerror(i));
    return -1;
  }

  args = (char **)poptGetArgs(optCon);
  if(!args) {
    fprintf(stderr, "No devices given\n");
    return -1;
  }

  /* 
   * Make sure mandatory arguments are given
   */
  if(!opt_read && !opt_write) {
    fprintf(stderr, "Must either read or write\n");
    return -1;
  }
  if(opt_read && opt_write) {
    fprintf(stderr, "Cannot both read and write\n");
    return -1;
  }
  if(array_level != 0 && array_level != 30) {
    fprintf(stderr, "We only know raid level 0 and 30 (single disk) for now\n");
    return -1;
  }
  if(chunk_size <= 0) {
    fprintf(stderr, "Chunk size must be specified\n");
    return -1;
  }
  if(array_size <= 0) {
    fprintf(stderr, "Array size must be specified\n");
    return -1;
  }

  /*
   * Choose action
   */
  if(opt_write) {
    switch(array_level) {
    case 0:
      return write_raid0(); 
    case 30:
      return write_single();
    default:
      fprintf(stderr, "Unknown level\n");
      return -1;
    }
  }

  if(opt_read) {
    switch(array_level) {
    case 0:
      return read_raid0();
    case 30:
      return read_single();
    default:
      fprintf(stderr, "Unknown level\n");
      return -1;
    }
  }

  return 0;
}

void print_fillword(const struct fillword* fw)
{
  fprintf(stderr, 
	  "block_num   = %u\n"
	  "org_level   = %i\n"
	  "org_dchunk  = %u\n"
	  "padding     = %i\n",
	  fw->block_num, fw->org_level,
	  fw->org_dchunk, fw->padding);
}

void init_fillword(struct fillword* fw, unsigned gblock, unsigned dchunk)
{
  fw->block_num = gblock;
  fw->org_level = array_level;
  fw->org_dchunk = dchunk;
  fw->padding = PADNUM;
}

void init_block_buf(char* buf, unsigned gblock, unsigned dchunk)
{
  int pos;
  for(pos = 0; pos != GBLK_SIZE / sizeof(struct fillword); pos++)
    init_fillword((struct fillword*)(buf + sizeof(struct fillword) * pos), gblock, dchunk);
}

int check_fillword(const struct fillword* fw, unsigned gblock, unsigned dchunk)
{
  if(fw->block_num != gblock) {
    fprintf(stderr, "\n*** Mismatch at gblock = %u, dchunk = %u\n", gblock, dchunk);
    print_fillword(fw);
    return 1;
  } else if(fw->padding != PADNUM) {
    fprintf(stderr, "\n*** Bad padding at gblock = %u, dchunk = %u\n", gblock, dchunk);
    print_fillword(fw);
    return 1;
  }
  return 0;
}

int check_block_buf(const char* buf, unsigned gblock, unsigned dchunk)
{
  int pos;
  int rc;
  for(pos = 0; pos != GBLK_SIZE / sizeof(struct fillword); pos++) 
    if((rc = check_fillword((const struct fillword*)(buf + sizeof(struct fillword) * pos), gblock, dchunk)))
      return rc;
  return 0;
}

void ary_progress(const char* dev, unsigned now, unsigned total)
{
  fprintf(stderr, "\r%s:  %9u/%-9u  ~%3i%%", dev, now, total, now * 100 / total);
  fflush(stderr);
}

void endary_progress(const char* dev)
{
  fprintf(stderr, "\r%s:  all done.                  \n", dev);
}


int write_single(void)
{
  FILE * fp = 0;
  unsigned blocks;

  if(!args[0] || args[1]) {
    fprintf(stderr, "Must have precisely one device\n");
    return -1;
  }
  if(!(fp = fopen(args[0], "w"))) {
    fprintf(stderr, "Cannot open %s for writing\n", args[0]);
    return -1;
  }

  for(blocks = 0; blocks != array_size; blocks++) {
    char buf[GBLK_SIZE];
    init_block_buf(buf, blocks, blocks / chunk_size);
    fwrite(buf, GBLK_SIZE, 1, fp);
    ary_progress(args[0], blocks, array_size);
  }
  endary_progress(args[0]);

  fclose(fp);
  return 0;
}


int read_single(void)
{
  FILE * fp = 0;
  unsigned blocks;

  if(!args[0] || args[1]) {
    fprintf(stderr, "Must have precisely one device\n");
    return -1;
  }
  if(!(fp = fopen(args[0], "r"))) {
    fprintf(stderr, "Cannot open %s for writing\n", args[0]);
    return -1;
  }

  for(blocks = 0; blocks != array_size; blocks++) {
    char buf[GBLK_SIZE];
    int rc;
    if(1 != fread(buf, GBLK_SIZE, 1, fp)) {
      fprintf(stderr, "\nCannot read\n");
      return -1;
    }
    if((rc = check_block_buf(buf, blocks, blocks / chunk_size)))
      return rc;
    ary_progress(args[0], blocks, array_size);
  }
  endary_progress(args[0]);

  fclose(fp);
  return 0;
}


int write_raid0(void)
{
  FILE * fp = 0;
  unsigned blocks;
  char ** cur_dev;
  mdp_super_t sb;
  int ndisks = 0;
  int curdisk = 0;

  if(!args[0]) {
    fprintf(stderr, "Must have one or more devices\n");
    return -1;
  }

  for(cur_dev = args; *cur_dev; cur_dev++)
    ndisks ++;

  for(cur_dev = args; *cur_dev; cur_dev++,curdisk++) {
    if(!(fp = fopen(*cur_dev, "w"))) {
      fprintf(stderr, "Cannot open %s for writing\n", *cur_dev);
      return -1;
    }
    
    for(blocks = curdisk; blocks-curdisk < array_size; blocks += ndisks) {
      char buf[GBLK_SIZE];
      init_block_buf(buf, blocks, (blocks-curdisk) / chunk_size);
      fwrite(buf, GBLK_SIZE, 1, fp);
      ary_progress(*cur_dev, blocks, array_size);
    }
    endary_progress(*cur_dev);

    /* Put a superblock in there too */
    if(fseek(fp, 1024 * MD_NEW_SIZE_BLOCKS(array_size/ndisks), SEEK_SET)) {
      fprintf(stderr, "Cannot seek to superblock position: %s\n", strerror(errno));
      return -1;
    } else fprintf(stderr, "Pos: %lu\n", MD_NEW_SIZE_BLOCKS(array_size/ndisks));
    memset(&sb, 0, sizeof(sb));
    sb.md_magic = MD_SB_MAGIC;
    sb.state = (1 << MD_SB_CLEAN);
    fwrite(&sb, sizeof(sb), 1, fp);

    fclose(fp);
  }

  return 0;
}

int read_raid0(void)
{
  FILE * fp = 0;
  unsigned blocks;
  char ** cur_dev;
  int ndisks = 0;
  int curdisk = 0;

  if(!args[0]) {
    fprintf(stderr, "Must have one or more devices\n");
    return -1;
  }

  for(cur_dev = args; *cur_dev; cur_dev++)
    ndisks ++;

  for(cur_dev = args; *cur_dev; cur_dev++,curdisk++) {
    unsigned dblocks_read = 0;

    if(!(fp = fopen(*cur_dev, "r"))) {
      fprintf(stderr, "Cannot open %s for writing\n", *cur_dev);
      return -1;
    }
    
    for(blocks = curdisk; blocks-curdisk < array_size; blocks += ndisks) {
      char buf[GBLK_SIZE];
      int rc;
      fread(buf, GBLK_SIZE, 1, fp);
      dblocks_read ++;

      if(dblocks_read == MD_NEW_SIZE_BLOCKS(array_size/ndisks))
	break;

      if((rc = check_block_buf(buf, blocks, (blocks-curdisk) / chunk_size)))
	return rc;
      ary_progress(*cur_dev, blocks, array_size);
    }
    endary_progress(*cur_dev);

    fclose(fp);
  }

  return 0;
}

